JSP에서 DB를 가져오기 위한 방법을 배우던 중
JDBC에 대한 개념이 필요해 정리해본다.
사실 그 이전부터 어떻게 DB와 웹이 연결되어서
데이터를 가지고 오는지 그게 너무너무 궁금했다.
이렇게 그 궁금증이 해소되어 뿌듯하고
조급하지 않게 공부해야겠다.

SQL문을 직접 작성했다.
이 작성한 내용을 가지고 ORACLE이나 MS SQL서버에 접속했다.
그러나 직접 서버에 접속하지 않고 DB Client 프로그램을 통해서 실행했다.
나는 ORACLE에서 제공하는 SQL Developer를 통해서 접속했다.
이 Client 프로그램은 연결/인증, 문장실행, 결과패치 기능을 제공한다.

그러나 실제 사용자(네이버, 다음 이용자등)는 DB를 만들 때 SQL문을 통해서 만드는 것이 아니라
프로그래머가 만든 UI를 통해서 간접적으로 DB를 생성하고 조회하게 된다.
회원 목록을 달라고 하면 그 UI를 통해서 SQL문을 만들고 연결/인증, 문장실행, 결과패치 등의 기능을 통해서 ORACLE DBMS에서 접속해서 데이터를 가져오게 된다.
결론적으로 우리(프로그래머)는 사용자가 UI를 통해서 간접적으로 SQL문을 만들고 연결/인증, 문장실행, 결과패치 기능을 하는 즉 코드에서 DataBase를 이용할 수 있도록 하는 library가 필요하다. 바로 DB API이며 이와 연결하여 ORACLE, MS SQL과 같은 DBMS에 접근할 수 있도록 해야 한다.
그러나 ORACLE의 DB API와 MS SQL의 DB API는 기능은 같으나 함수의 이름은 다르다. 따라서 DB를 연결하기 위한 DBC(DataBase Connectivity)가 차이가 있기 때문에 만약에 ORACLE에서 MS SQL로 바꾸는 경우 코드를 다 바꿔야한다.

그래서 등장한 것이 JDBC이다.
ORCLE이나 MS SQL을 구동하고 접속하기 위한 코드들(JDBC Driver)은 따로 있으며 (DBMS에서 제공하기 때문에 따로 다운로드 받아야한다.) 이를 직접 쓰지 않고 간접적으로 이용할 수 있도록 단일화된 도구를 이용한다. 이를 JDBC라고 하며 JAVA에서 제공한다.
✔️ JDBC를 사용하기 위한 절차
1. JDBC Driver로드하기 : 실질적인 일을 하는 JDBC Driver 로드하기 (JDBC는 깡통이다.)
2. 연결 생성하기
3. 문장 실행하기
4. 결과집합 사용하기
package ex1;
import java.sql.Connection;
public class Program {
public static void main(String[] args) {
// TODO Auto-generated method stub
Connection con;
}
}
이렇게 Program.java 파일을 만들어 JDBC를 이용하기 위해서는 먼저
JDBC Driver를 설치한다.
설치할 때 압축파일을 풀지 말아야한다.
압축파일 자체를 지정해서 사용하기 때문이다.


위 페이지에서 Libraries에 들어가서 Modulepath를 누른 후 Add External JARs..버튼을 클릭하여 ojdbc8.jar파일이 있는 경로를 찾아 추가해준다.
실제 구동 코드는 JDBC Driver가 가지고 있다.

이 드라이브를 load해야만 DB를 연결하거나 실행이 가능

1단계 : Class.forName("oracle.jdbc.driver.OracleDriver");
이 OracleDriver를 객체화 시키는 것 그러나 new생성자를 통해서 객체화 시키지 않는다

2단계 : Connection con = DriverManager.getConnection(...);
1단계에서 생성한 객체(DriverManager)를 통해 연결 객체 Connection을 얻음

3단계 : Statement st = con.createStatement();
실행도구 생성, 사용자로부터 받은 쿼리를 실행

4단계 : ResultSet rs = st.executeQuery(sql);
실행한 결과를 ResultSet에 돌려줌
쿼리를 실행한 서버에는 결과 집합이 만들어진다.
서버는 이 결과집합을 한번에 보낼 수 없고
레코드 하나씩 하나씩 보내준다.
따라서 그 위치를 가리키는 커서와 이를 옮겨주는 빈그릇 하나가 필요하다.
이를 만들어주는 것이 ResultSet이다.
즉 Client 도구이다.
특이하게 객체가 4개가 만들어 지는데 new 연산자를 만들지 않는다.
그 이유는 서로 연결되어져 있기 때문에 따로 만들지 않는 것!

5단계 : rs. next();
패치의 단계 다음꺼를 계속 받을 수 있도록 커서를 옮겨주는
6단계: String title = rs.getString("title");
데이터를 뽑음
package ex1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Program {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "SELECT * FROM NOTICE";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
if(rs.next()){
String title = rs.getString("title");
//칼럼명 소대문자 구분 안함
System.out.println(title);
}
rs.close();
st.close();
con.close();
}
}

계속 빈 콘솔창만 떠서 여러번 확인했다.
일단은 오타들도 있었다. oracle을 orcle로 입력했거나 OracleDriver를 oracleDriver로 입력했다거나
하지만 다 고쳐도 계속 빈화면만 떴다.
그래서 계정 비밀번호가 문제인가 확인했다.
그것도 아니었다.
마지막으로 JDBC Driver 버전이 문제인가 마지막으로 확인했다.
내 jdk는 11버전인데 내가 Driver를 21버전으로 해서 문제가 있는거 같았다.
그래서 다시 Driver를 18버전으로 다운로드 해서 라이브러리를 설정했더니
성공했다.
따라서 빈 콘솔창이 뜰 때는
1. 오타
2. 계정
3. Driver 버전 문제
이 세가지를 살펴봐야 하는것 같다.

물음표로 된 부분과 데이터 전체 출력하기
내가 풀어본 코드
package ex1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
public class Program {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "SELECT * FROM NOTICE";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
int id = rs.getInt("id");
String title = rs.getString("title");
String writer_id=rs.getString("writer_id");
String content = rs.getString("content");
Date regdate = rs.getDate("regdate");
int hit = rs.getInt("hit");
//칼럼명 소대문자 구분 안함
System.out.printf("id:%d,title:%s,writer_id:%s, regDate:%s, content:%s, hit:%d\n"
,id,title,writer_id, regdate, content,hit);
}
rs.close();
st.close();
con.close();
}
}

두번째 문제 : 조회수가 10이상인 레코드를 출력하시오.
내가 풀어본 코드
package ex1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
public class Program {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "SELECT * FROM NOTICE";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
int id = rs.getInt("id");
String title = rs.getString("title");
String writer_id=rs.getString("writer_id");
String content = rs.getString("content");
Date regdate = rs.getDate("regdate");
int hit = rs.getInt("hit");
//칼럼명 소대문자 구분 안함
if(hit>10) {
System.out.printf("id:%d,title:%s,writer_id:%s, regDate:%s, content:%s, hit:%d\n"
,id,title,writer_id, regdate, content,hit);
}
}
rs.close();
st.close();
con.close();
}
}

if(hit>10) {
System.out.printf("id:%d,title:%s,writer_id:%s, regDate:%s, content:%s, hit:%d\n"
,id,title,writer_id, regdate, content,hit);
}
이렇게 if 문을 통해 출력
if(hit>10) {
System.out.printf("id:%d,title:%s,writer_id:%s, regDate:%s, content:%s, hit:%d\n"
,id,title,writer_id, regdate, content,hit);
}
바람직 하지 않음
sql에 자체를 바꿔버리기

나의 오답을 통해서 개념이 더 명확해졌다.
이제는 데이터에 대한 필터링, 정렬, 그룹화 등의 모든 데이터 연산을 JAVA에서 하지 않고 DATABASE에서 한다.
앞서 배웠던 SQL에 대한 복습이라고 볼 수 있다.
크게
1. 기본값 설정
2. 시퀀스 설정
3. 롤백
에 대해서 배운다.
기본값
일단 NOTICE라는 테이블에 기본값으로 주어질 HIT =0, REGDATE= SYSTIMESTAMP로 해야한다. 사용자가 입력할 수 없기 때문이다.
시퀀스
ID의 경우 시퀀스를 통해서 1씩 늘려가기로 하는데
지금 현재 데이터를 조회하면

16이기 때문에

17로 시작하는 시퀀스를 설정해주면 된다.
그렇다면 제대로 설정되었는지 테스트 해보자

제대로 설정되었다.
하지만 17이라는 ID번호는 소비되었기 때문에
INSERT를 통해서 데이터를 넣으면 그 ID는 18이 나온다.
이번 시간에 만드는 JAVA 파일 : Program2.java
Insert,Update,Delete 할 때는 결과집합이 없음.
따라서
ResultSet rs = st.executeQuery(sql); ➡️ int i = st.executeUpdate(sql);
이는 몇 개의 레코드만 반환되는지 그 갯수를 반환
Select 할 때만 ResultSet rs = st.executeQuery(sql);
따라서 이번 입력하는 Insert에서는 rs에 대한 부분은 모두 지우자.

여기를 보면 우리는 변수를 받고 이걸 sql에 꽂아야 하는데
엄청 복잡해보인다.
""+title+""이렇게만 쪼개지는 것이 아니라
String이기 때문에 작은 따옴표가 감싸야해서
다시 "'"+title+"'"이 된다.
이를 간단하게 할 수 있는 방법이 있는데
package ex1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
public class Program {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String title="TEST2";
String writerId="newlec";
String content="hahaha";
String files="";
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "INSERT INTO notice ("
+ " title,"
+ " writer_id,"
+ " content,"
+ " files"
+ ") VALUES (?,?,?,?)";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
//Statement st = con.createStatement();
PreparedStatement st = con.prepareStatement(sql);
//따라서 저 ?에 채워야 하는데
//실행할 때 st. execute..(sql) 넘기지 말고 미리
st.setString(1, title);
st.setString(2, writerId);
st.setString(3, content);
st.setString(4, files);
int result= st.executeUpdate();
System.out.println(result);
st.close();
con.close();
}
}


String sql = "INSERT INTO notice ("
+ " title,"
+ " writer_id,"
+ " content,"
+ " files"
+ ") VALUES (?,?,?,?)";
이렇게 ?로 하는 것이다.
이 후 여기에 값을 심어야 하는데
심기 위해 실행하기 전에 sql을 넘기고
심은 후 실행해야 한다.
따라서
PreparedStatement st = con.prepareStatement(sql);
//따라서 저 ?에 채워야 하는데
//실행할 때 st. execute..(sql) 넘기지 말고 미리
st.setString(1, title);
st.setString(2, writerId);
st.setString(3, content);
st.setString(4, files);
int result= st.executeUpdate();
UPDATE할 때는 어떤 것을 사용자가 수정할 수 있는지 칼럼의 특성을 파악하는 것이 중요하다.
내가 배우는 NOTICE 테이블의 경우 ID는 시퀀스를 통해서 자동부여되기 때문에 수정할 수 없고 REGDATE, HIT 수도 사용자가 수정할 수 없다.

UPDATE NOTICE
SET
TITLE='TEST3',
CONTENT='HAHAHA',
FILES=''
WHERE ID = 37;
를 통해서 수정한다.

수정되었으며 이를 JDBC를 통해서 적용하기 위해
ROLLBACK을 한다.

이번 시간에 만드는 JAVA 파일 : Program3.java

여기서 중요한 것이 있는데
내가 저기 빨간색으로 표시한 부분이다.
이 덧셈 문장을 사용할 때는 최종적으로 어떤 문장이 완성될 것인가 상상해야 한다.
만약에 저기에 띄어쓰기를 하지 않고
"UPDATE NOTICE"+
"SET" 이렇게 했다면 이는 "UPDATE NOTICESET"을 의미한다는 것을
염두해두고 띄어쓰기 유의해서 사용해야 한다.
package ex1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
public class Program3 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String title = "TEST3";
String content ="HAHAHA";
String files="";
int id = 37;
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "UPDATE NOTICE "
+ "SET"
+ " TITLE=?,"
+ " CONTENT=?,"
+ " FILES=?"
+ "WHERE ID = ?";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
//Statement st = con.createStatement();
PreparedStatement st = con.prepareStatement(sql);
st.setString(1,title);
st.setString(2,content);
st.setString(3,files);
st.setInt(4, id);
int result=st.executeUpdate();
System.out.println(result);
st.close();
con.close();
}
}


이번 시간에 만드는 JAVA 파일 : Program4.java
package ex1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
public class Program4 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
int id = 37;
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "DELETE NOTICE WHERE ID = ?";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
//Statement st = con.createStatement();
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1, id);
int result=st.executeUpdate();
System.out.println(result);
st.close();
con.close();
}
}


ID 37번의 데이터가 사라졌다.
CRUD(INSERT,SELECT,UPDATE,DELETE)(CREATE,RETRIEVE,UPDATE,DELETE)
이전까지는 CRUD 각각의 기능을 따로 만들었지만
이제는 저 기능 main함수에서 떼어내어 한번에 할 수 있도록 할 것이다.

package명 : 클래스를 식별할 수 있는 이름으로 확장, 저 클래스 명을 충돌하지 않게 하기 위해서 만약에 다른 회사에서 NoticeService라는 클래스가 있을 수 있으니까 패키지명을 지정함으로써 이름이 충돌하지 않도록 하는 것이다.
✔️ NoticeService.java : 서비스를 하는, 따라서 main함수가 없음
getList()라는 함수를 통해서 list의 인덱스로써 레코드들 반환한다. (select기능)
하지만 레코드는 여러 속성들로 이뤄어져 있기 때문에 이 레코드를 담을 그릇이
필요하고 그 그릇을 우리는 Notice.class를 통해서 구현한다.
package com.newlecture.app.service;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.newlecture.app.entity.Notice;
public class NoticeService {
public List<Notice> getList() throws ClassNotFoundException, SQLException{
String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "SELECT * FROM NOTICE";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url,"NEWLEC","비밀번호");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
List<Notice> list = new ArrayList<Notice>();
while(rs.next()) {
int id = rs.getInt("id");
String title = rs.getString("title");
String writer_id=rs.getString("writer_id");
String content = rs.getString("content");
Date regdate = rs.getDate("regdate");
int hit = rs.getInt("hit");
Notice notice = new Notice(
id,
title,
writer_id,
content,
regdate,
hit
);
//칼럼명 소대문자 구분 안함
list.add(notice);
}
rs.close();
st.close();
con.close();
return list;
}
}
✔️ Notice.java : 레코드를 담은 그릇
package com.newlecture.app.entity;
import java.util.Date;
public class Notice {
private int id ;
private String title ;
private String writer_id;
private String content ;
private Date regdate ;
private int hit ;
public Notice() {
}
public Notice(int id, String title, String writer_id, String content, Date regdate, int hit) {
this.id = id;
this.title = title;
this.writer_id = writer_id;
this.content = content;
this.regdate = regdate;
this.hit = hit;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getWriter_id() {
return writer_id;
}
public void setWriter_id(String writer_id) {
this.writer_id = writer_id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getRegdate() {
return regdate;
}
public void setRegdate(Date regdate) {
this.regdate = regdate;
}
public int getHit() {
return hit;
}
public void setHit(int hit) {
this.hit = hit;
}
}
구조가 노출되지 않도록 private를 넣었다.


👆 마우스 오른쪽 키 누르고 각 변수에 대해서 값들을 세팅하기 위해 getter and setter함수 만들기

👆 데이터 값을 채울 때 여러가지 용도로 초기화하면서 값들을 채우기 위해서 생성자 하나를 만든다.


👆 생성자 외에 필드를 가진 생성자를 만들기 위해
앞서서 getList()함수를 통해서 select 기능을 구현했다면
이제 나머지 insert, update, delete 기능을 구현하는 함수를 만들어보고자 한다.
✔️ NoticeService.java
NoticeServie 클래스에 insert(Notice notice), update(Notice notice), delete(int id) 함수를
추가해보자
package com.newlecture.app.service;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.newlecture.app.entity.Notice;
public class NoticeService {
private String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
private String uid ="NEWLEC";
private String pwd ="비밀번호";
private String driver ="oracle.jdbc.driver.OracleDriver";
public List<Notice> getList() throws ClassNotFoundException, SQLException{
String sql = "SELECT * FROM NOTICE";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
List<Notice> list = new ArrayList<Notice>();
while(rs.next()) {
int id = rs.getInt("id");
String title = rs.getString("title");
String writer_id=rs.getString("writer_id");
String content = rs.getString("content");
Date regdate = rs.getDate("regdate");
int hit = rs.getInt("hit");
String files = rs.getString("files");
Notice notice = new Notice(
id,
title,
writer_id,
content,
regdate,
hit,
files
);
//칼럼명 소대문자 구분 안함
list.add(notice);
}
rs.close();
st.close();
con.close();
return list;
}
public int insert(Notice notice) throws SQLException, ClassNotFoundException {
String title=notice.getTitle();
String writerId=notice.getWriter_id();
String content=notice.getContent();
String files=notice.getFiles();
String sql = "INSERT INTO notice ("
+ " title,"
+ " writer_id,"
+ " content,"
+ " files"
+ ") VALUES (?,?,?,?)";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, title);
st.setString(2, writerId);
st.setString(3, content);
st.setString(4, files);
int result= st.executeUpdate();
System.out.println(result);
st.close();
con.close();
return result;
}
public int update(Notice notice) throws ClassNotFoundException, SQLException {
String title = notice.getTitle();
String content =notice.getContent();
String files=notice.getFiles();
int id = notice.getId();
String sql = "UPDATE NOTICE "
+ "SET"
+ " TITLE=?,"
+ " CONTENT=?,"
+ " FILES=?"
+ "WHERE ID = ?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
PreparedStatement st = con.prepareStatement(sql);
st.setString(1,title);
st.setString(2,content);
st.setString(3,files);
st.setInt(4, id);
int result=st.executeUpdate();
st.close();
con.close();
return result;
}
public int delete(int id) throws ClassNotFoundException, SQLException {
String sql = "DELETE NOTICE WHERE ID = ?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
//Statement st = con.createStatement();
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1, id);
int result=st.executeUpdate();
st.close();
con.close();
return result;
}
}
각각의 함수에서 url과 서버 접속 아이디, 비밀번호 그리고 드라이버 주소가 같기 때문에 이 부분은 앞으로 뺐다.
한가지 드는 의문은
Connection con = DriverManager.getConnection(url,uid,pwd);
//Statement st = con.createStatement();
PreparedStatement st = con.prepareStatement(sql);
이 부분도 공통적인 부분이라 빼고 싶다고 생각하겠지만
그럴 수 없다.
DB에 접속할 때마다 만들어줘야 하기 때문에
하나를 만들고 계속 사용할 수 없다.
다음 시간에는 사용자로부터 저 Notice를 입력받는 방법에 대해서
배워보도록 하자.
전체적으로 어떤 파일들이 필요한지 먼저 정리해보자
NoticeService.java : 실제 JDBC가 실행되는 곳 , CRUD 서비스가 한 곳에 정리되어져 있고 각각 기능에 맞는 함수 4개가 정의되어져 있다.
Notice.java : 레코드를 담는 그릇 레코드 특성 상 속성 여러개의 값으로 이루어져 있기 때문에 그릇이 필요하다.
Program5.java : main 함수가 있는 실제 실행되는 곳
NoticeConsole.java : 사용자 UI가 보이는 곳

따라서 우리는 이 4개의 클래스들을 이용한다.

실제 콘솔창에는 위처럼 출력이 되어야 한다.
그리고 사용자로부터 입력을 받는데 1을 입력하면 상세조회, 2를 입력하면 이전, 3은 다음, 4는 글쓰기로 넘어갈 수 있도록 설정한다.
먼저
main함수가 있는 Program5.java 코드이다.
package ex1;
import java.sql.SQLException;
import com.newlecture.app.console.NoticeConsole;
public class Program5 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
NoticeConsole console = new NoticeConsole();
console.printNoticeList();//목록을 보여주는
int menu = console.inputNoticeMenu(); //입력받는
//switch문을 통한 설정값 넘기기
switch(menu) {
case 1: // 상세조회
break;
case 2: // 이전
break;
case 3: // 다음
break;
case 4: // 글쓰기
break;
}
}
}
👆 총 2가지이다.
1. 콘솔창 보여주기
2. 입력받은 값으로 1,2,3,4 중 설정값으로 넘기기
다음은 콘솔창을 구현한 NoticeConsol.java이다.
package com.newlecture.app.console;
import java.sql.SQLException;
import java.util.List;
import com.newlecture.app.entity.Notice;
import com.newlecture.app.service.NoticeService;
public class NoticeConsole {
private NoticeService service;
public NoticeConsole() {
service = new NoticeService();
}
public void printNoticeList() throws ClassNotFoundException, SQLException {
List<Notice> list = service.getList();
System.out.println("────────────────────────────────────────");
System.out.printf("<공지사항> 총 %d 게시글\n",12);
System.out.println("────────────────────────────────────────");
for(Notice n : list) {
System.out.printf("%d. %s/ %s / %s\n"
,n.getId()
,n.getTitle()
,n.getWriter_id()
,n.getRegdate());
}
System.out.println("────────────────────────────────────────");
System.out.printf(" %d/%dpages\n",1,2);
}
public int inputNoticeMenu() {
return 0;
}
}
여기서 바보처럼
Exception in thread "main" java.lang.NullPointerException:
이 에러가 떴는데
보통 이 경우는 뭔가 빈 객체가 있다는 거다.
선언은 해놓고선 아무것도 만들지 않았거나
선언만 해놓고 사용한 경우
private NoticeService service;
public NoticeConsole() {
service = new NoticeService();
}
나 같은 경우 저렇게 NoticeService service를 선언해놓고
객체를 만들지 않았다.
즉 new NoticeService()
이걸 만들지 않아서 오류가 발생했다.

해결하고 위와 같은 결과가 나왔다.
하지만 모든 레코드가 출력되었고 아직 1,2,3,4 입력을 받는 부분을 구현하지 않았다.
program5.java switch문
EXIT:
while(true) {
console.printNoticeList();
int menu = console.inputNoticeMenu();
switch(menu) {
case 1: // 상세조회
break;
case 2: // 이전
break;
case 3: // 다음
break;
case 4: // 글쓰기
break;
case 5:
System.out.println("Bye~~");
break EXIT;
default:
System.out.println
("<<사용방법>> 메뉴는 1~4까지만 입력할 ㅜㅅ 있습니다.");
break;
}
}
break문은 여기서 switch를 나가기 때문에
break EXIT를 통해 labeling을 이용하여 while문을 나가도록 설정했다.
NoticeConsole.java에서는
public int inputNoticeMenu() {
Scanner scan = new Scanner(System.in);
System.out.print("1.상세조회/2.이전/3.다음/4.글쓰기/5.종료>");
String menu_ = scan.nextLine();
int menu = Integer.parseInt(menu_);
return menu;
}
menu를 클릭한 값을 입출력하는 콘솔창을 만드는 함수를 정의하였다.

여기서는 SQL에서 배우는 ROWNUM이 등장한다.
ROWNUM은 무조건 1부터 시작하고
1부터 쌓인다.
따라서 만약에
SELECT ROWNUM, NOTICE.* FROM NOTICE
WHERE ROWNUM BETWEEN 2 AND 10;
이렇게 하면
1부터 하나씩 쌓이기 때문에 쌓이지 못하고 다시
또 다시
또 다시 결국은 오류가 발생한다.
따라서 만약에 우리가 1부터 10까지 페이징 하고
11부터 20까지 페이징을 하고 싶다면 저 방법으로는 불가능하다.
이를 해결하는 방법은 ROWNUM자체에 별칭을 부여하는 테이블을 만들고
이 테이블을 조회하는 것이다.
SELECT FROM (SELECT ROWNUM NUM, NOTICE. FROM NOTICE)
WHERE NUM BETWEEN 2 AND 10;
이렇게 말이다.
이건 가능하다.
또한 한가지 더 문제가 있는데
만약에 우리가 REGDATE를 최신순으로 10개씩 페이징하고 한다면
저기 서브쿼리에 ORDER BY REGDATE DESC를 추가하면 될거 같다.
하지만 이 또한 불가능하다.
SELECT FROM (SELECT ROWNUM NUM, NOTICE. FROM NOTICE ORDER BY REGDATE DESC)
WHERE NUM BETWEEN 1 AND 10;
왜 그럴까?
보면 ROWNUM은 ORDER BY전에 생성되는 일련번호이기 때문이다.
우리가 원하는 것은 최신순으로 정렬하고 일련번호를 붙이고 싶지만
저 SELECT문은 일련번호를 붙이라고 ORDER BY를 실행한다.

따라서 우리는 정렬한 다음에 일련번호를 붙이도록 서브 쿼리를 다시 넣어야 한다.
SELECT
FROM (SELECT ROWNUM NUM, N. FROM (SELECT * FROM NOTICE ORDER BY REGDATE DESC) N)
WHERE NUM BETWEEN 1 AND 10;
자 이제 완성이 되었다.

17강을 통해 배운 페이징 쿼리문을
NoticeService 클래스의 getList() 함수에 넣기로 한다.
또한 getList(int page)를 보면 page를 매개변수로 받아서
출력하기로 하는데
String sql = "SELECT "
+ "FROM (SELECT ROWNUM NUM,N. FROM (SELECT * FROM NOTICE ORDER BY REGDATE DESC) N)"
+ "WHERE NUM BETWEEN '"+start+"' AND '"+end+"'";
이렇게 표현해도 되지만 더 간단한 표현을 위해서 PreparedStatement를 통해서 변수를 심기로 한다.
따라서 그 전에 사용하면 Statement st = con.createStatement();가 아닌
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1,start);
st.setInt(2,start);
로 변수를 심고 이제
서버에서 커서와 이걸 옮겨줄 그릇을 생겅하는
ResultSet을 만들어야 한다.
public List<Notice> getList(int page) throws ClassNotFoundException, SQLException{
int start = 1+ +(page-1)*10;
int end = 10*page;
String sql = "SELECT * "
+ "FROM (SELECT ROWNUM NUM,N.* FROM (SELECT * FROM NOTICE ORDER BY REGDATE DESC) N)"
+ "WHERE NUM BETWEEN ? AND ?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1,start);
st.setInt(2, end);
ResultSet rs = st.executeQuery();
List<Notice> list = new ArrayList<Notice>();
while(rs.next()) {
int id = rs.getInt("id");
String title = rs.getString("title");
String writer_id=rs.getString("writer_id");
String content = rs.getString("content");
Date regdate = rs.getDate("regdate");
int hit = rs.getInt("hit");
String files = rs.getString("files");
Notice notice = new Notice(
id,
title,
writer_id,
content,
regdate,
hit,
files
);
//칼럼명 소대문자 구분 안함
list.add(notice);
}
rs.close();
st.close();
con.close();
return list;
}
따라서 위와 같이 같이 코드를 설정하고
NoticeConsole.java에서
public void printNoticeList() throws ClassNotFoundException, SQLException {
List<Notice> list = service.getList(1);
위와 같이 getList(1);
을 넣으면 1page에 해당하는 게시글을 보여준다.

우리는 SQL문에서 VIEW에 대해서 배웠다.
이를 활용하자
지저분하고 길었던
SELECT * FROM (
SELECT ROWNUM NUM, N.* FROM (
SELECT * FROM NOTICE ORDER BY REGDATE DESC) N)
WHERE NUM BETWEEN 1 AND 10;
이걸 VIEW로 만들거다.
CREATE VIEW NOTICE_VIEW
AS
SELECT * FROM (
SELECT ROWNUM NUM,N.* FROM (
SELECT * FROM NOTICE ORDER BY REGDATE DESC
) N);
SELECT * FROM NOTICE_VIEW
WHERE NUM BETWEEN 11 AND 20;
다시 NoticeService 클래스에서
getList(int page)에 있는 sql문이

위와 같이 아주 간단해졌다.
2를 누르면 이전 페이지로
3을 누르면 다음 페이지로 가서 목록을 출력하는 것을 구현해보자
먼저 main() 함수가 있는 Program5.java이다.

그리고 콘솔창을 보여주는 NoticeConsole.java이다.

1. page라는 변수를 NoticeConsole 클래스의 변수로 선언해서 값을 조정하고
2. Program5의 while문을 통해서 printNoticeList() 함수에서
3. getList(조정된 페이지) 를 통해 레코드를 가져와 콘솔창에 출력한다.

위를 보면 총 게시글의 개수가 12로 고정되어져 있다.
내가 저기 12라는 값을 직접 넣었기 때문인데
실제 게시글의 개수를 구해보자

일단 NoticeConsole.java의 count라는 변수를 지정하고 이를 출력해야 한다.
실제 count의 값은 NoticeService 클래스의 getCount()함수를 통해서 얻어오기로 한다.
public class NoticeService {
private String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
private String uid ="NEWLEC";
private String pwd ="비밀번호";
private String driver ="oracle.jdbc.driver.OracleDriver";
...
//Scalar : 단일값을 얻는
public int getCount() throws ClassNotFoundException, SQLException {
int count =0;
String sql = "SELECT COUNT(ID) COUNT FROM NOTICE";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
if(rs.next())
count = rs.getInt("COUNT");
rs.close();
st.close();
con.close();
return count;
}
위와 같이 NoticeService 클래스에 getCount()함수를 추가했다.


여기서 살펴볼 것은 앞서 전역변수로 선언했던 count가 지역변수로 들어갔다는 것이다.
그 이유는 count는 printNoticeList()가 선언될 때 계속바뀌기 때문에
그 값을 저 함수 내에서만 사용하지 다른 곳에서 사용할 수 없다.
또한 moverNestList()에서 count가 사용되어 전역변수로 사용해야 하는 것이 아닌까
의문이 들 수 있지만 count라는 변수를 insert,delete를 통해서 수시로 바뀌기 때문에
전역변수로 사용하는 것은 바람직하지 않다.
마찬가지로 lastPage로 위와 같은 이유로 지역변수로 선언되는 것이 바람직하다.
switch(menu) {
case 1: // 상세조회
break;
case 2: // 이전
console.movePrevList();
break;
case 3: // 다음
console.moveNestList();
break;
case 4: // 글쓰기
break;
case 5: // 검색
console.inputSearchWord();
break;
case 6:
System.out.println("Bye~~");
break EXIT;
default:
System.out.println
("<<사용방법>> 메뉴는 1~4까지만 입력할 수 있습니다.");
break;
}
Program5.java의 switch문에 case 5:를 추가했다.
함수를 보면 console.inputSearchWord()를 호출하기 때문에
NoticeConsole.java에서 저 함수를 아래와 같이 정의하자
public void inputSearchWord() {
Scanner scan = new Scanner(System.in);
System.out.println("검색 범주(title/content/writer_id)중 하나를 입력하세요.");
System.out.print(">");
searchField = scan.nextLine();
System.out.print("검색어>");
searchWord=scan.nextLine();
}

따라서 위와 같은 결과가 나오는데
아직 검색 기능은 하지 못하기 때문에
Program5의 while문의 console.printNoticeList();에 의해서
같은 페이지가 계속 출력된다.
NoticeService 클래스의 getList 함수를 수정하자.
public class NoticeService {
private String url ="jdbc:oracle:thin:@localhost:1521/xepdb1";
private String uid ="NEWLEC";
private String pwd ="비밀번호";
private String driver ="oracle.jdbc.driver.OracleDriver";
public List<Notice> getList(int page, String field, String query) throws ClassNotFoundException, SQLException{
//field: 범주, query: 검색어
int start = 1+ +(page-1)*10;
int end = 10*page;
String sql = "SELECT * FROM NOTICE_VIEW "
+"WHERE "+field+" LIKE ? NUM BETWEEN ? AND ?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid,pwd);
PreparedStatement st = con.prepareStatement(sql);
st.setString(1,"%"+query+"%");
st.setInt(2,start);
st.setInt(3, end);
...
}
...
}
일단 SELECT문을 출력하는 getList(int page)라는 함수는
단순히 page를 받는 것이 아니라 이제는 검색기능이 추가되어져야 한다.
따라서 getList(int page, String field, String query)로 고쳐져야 한다.
이제 저 매개변수를 sql문에 넣어야 하는데
현재 sql문은
String sql = "SELECT * FROM NOTICE_VIEW "
+"WHERE NUM BETWEEN ? AND ?";
이렇게 선언되어져 있다.
그렇다면 우리는 이렇게 넣고 싶다.
String sql = "SELECT * FROM NOTICE_VIEW WHERE title LIKE '%검색어%' AND NUM BETWEEN ? AND ?";
이렇게 말이다.
1단계 : "SELECT * FROM NOTICE_VIEW WHERE ? LIKE ? AND NUM BETWEEN ? AND ?";
st.setString(1, title);
st.setString(2, "%"+검색어+"%");
이렇게 하면되는 거 아닌가?
틀리다.
저렇게 넣으면
"SELECT * FROM NOTICE_VIEW WHERE 'title' LIKE '%검색어%' AND NUM BETWEEN ? AND ?"
query는 알맞게 들어가지만
title은 값이 아닌 속성명이기 때문에 저렇게 ?를 통해서 넣을 수 없다.
따라서
2단계 : "SELECT * FROM NOTICE_VIEW WHERE "+field+" LIKE ? AND NUM BETWEEN ? AND ?";
st.setString(1, "%"+검색어+"%")
이렇게 선언해줘야 한다.
그리고 이제
NoticeConsole에 들어가서
public class NoticeConsole {
private NoticeService service;
private int page;
private String searchField;
private String searchWord;
public NoticeConsole() {
service = new NoticeService();
page =1;
searchField="";
searchWord="";
}
public void printNoticeList() throws ClassNotFoundException, SQLException {
List<Notice> list = service.getList(page,searchField,searchWord);
...
}
...
public void inputSearchWord() {
Scanner scan = new Scanner(System.in);
System.out.println("검색 범주(title/content/writer_id)중 하나를 입력하세요.");
System.out.print(">");
searchField = scan.nextLine();
System.out.print("검색어>");
searchWord=scan.nextLine();
}
}
이렇게 getList의 매개변수가 늘어 getList(page,searchField,searchWord)이렇게 선언해준다.
앞서서 우리가 searchField와 searchWord를 전역변수로 선언한 이유는 다른 함수에서 같은 변수를 사용하기 위함이다.
이렇게 하고 Program5.java를 들어가 run을 돌리면
Caused by: Error : 936, Position : 33, Sql = SELECT FROM NOTICE_VIEW WHERE LIKE :1 NUM BETWEEN :2 AND :3 , OriginalSql = SELECT FROM NOTICE_VIEW WHERE LIKE ? NUM BETWEEN ? AND ?, Error Msg = ORA-00936: 누락된 표현식
at ojdbc8/oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:498)
... 17 more
위와 같은 에러가 발생하는데 그 이유는 searchField의 초기값을 ""로 설정하여 WHERE LIKE 사이에 필드 값이 들어가지 않기 때문이다.
따라서 searchField의 초기값을 아무 속성으로 설정한다.
나는 title로 설정했다.

위와 같은 결과가 나온다.
이로써 JDBC 강의는 완강이다.
다시 Servlet/JSP강의 들으러 가야한다.