Programming/JDBC

CRUD 서비스 만들기, SQL 페이징 쿼리의 이용(ROWNUM).2022-02-14

최동훈1 2022. 2. 14. 21:58

우선적으로 사용자에게 DB의 테이블들을 보여주기 위해서는 전부 다 보여주는 것이 아니라, 특정한 갯수대로 끊어서 보여주어야 한다.

ROWNUM에 대한 자세한 설명은 내가 SQL공부할떄 작성했던 포스팅을 참고:

ROWNUM을 이용한 페이징 출력, 중복제거 distinct 키워드, 문자열 자르기 SUBSTR 함수. 2021-07-29 — "꾸준하게, 지속하자" (tistory.com)

select * from (select RowNUM as num, N.* from
(select * from notice ORDER BY REGDATE DESC)
as N
)
where num   between 1 and 10;

이렇게 2개의 서브쿼리를 사용하면 페이징을 구현 할 수 있다.  

이유는 문법에 있다. SQL의 select 문에서 구절을 실행하는 순서는 

from -> where -> having -> group by -> order by -> select 이다.

예를들면,

select * from(select ROWNUM num, NOTICE.* from notice order by regdate DESC)
where num between 1 and 10;

이렇게 했을때는 아무 문제 없어 보이지만, 문법적 순서에 의해, (where절 단계: ROWNUM이 생성된 후) (order by절 단계 :  regdate기준 내림차순 정렬) 순서대로 이루어지기에, ROWNUM이 붙여진 후, 내림차순 정렬이 되어버려서 ROWNUM이 뒤죽박죽이 되어버린다.

 

그래서 서브쿼리를 이용해서, 미리 REGDATE 내림차순 정렬을 한 뒤, ROWNUM을 붙여주는 것이다.

그런데, 너무 복잡하다. 그래서 앞의 서브쿼리를 한번에 묶는 뷰를 만들어준다.

create view NOTICE_VIEW
as
select * from (select RowNUM as num, N.* from
(select * from notice ORDER BY REGDATE DESC)
N
)

이렇게 뷰를 생성하면, 앞으로는 굳이 2개의 서브쿼리를 이용한 코드의 복잡성을 줄일 수 있다.

select * from NOTICE_VIEW where NUM between 1 and 10;

이렇게 쉽게 쓴다.

 

이 쿼리를 이용해서 JDBC를 통해 CRUD 서비스를 만들어 보자.

 

일단 생각을 해야한다. 어떤 기능을 어느 클래스에 책임을 부여해서 나눌것인지 정해야 한다.

 

1.CRUD 서비스를 만들것이다. -> 무엇이 필요하냐? > DB에서 데이터를 일단 가져와야한다. -> NOTICESERVICE 클래스에 해당 책임을 부여하자.

package com.newlecture.app.service;

import java.sql.Connection;
import java.sql.Date;
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.List;

import com.newlecture.app.entity.Notice;

public class NoticeService {

	private String url="jdbc:oracle:thin:@localhost:1521/xepdb1";
	private String user="NEWLEC";
	private String PW="vdongv1620";
	
public List<Notice> getNoticeList() throws SQLException, ClassNotFoundException{
		ArrayList<Notice> list=new ArrayList<>();
		String sql="select * from notice";
		
		Class.forName("oracle.jdbc.driver.OracleDriver");
		Connection con=DriverManager.getConnection(url,user,PW);
		Statement st=con.createStatement();
		ResultSet rs=st.executeQuery(sql);
		
		while(rs.next()) {
			int ID =rs.getInt("ID");
			String title=rs.getString(2);
			String writer=rs.getString(3);
			String content=rs.getString(4);
			Date Date=rs.getDate(5);
			int hit=rs.getInt(6);
			String Files= rs.getString(7);
			Notice notice= new Notice( ID,
					                  title,
					                  writer,
					                  content,
					                  Date,
					                  hit,
					                  Files
					                 );
			
			list.add(notice);
		}
	return list;	
	}

public int insert(Notice notice) throws SQLException, ClassNotFoundException {
	
	String Title=notice.getTitle();
	String writer_id=notice.getWriter_id();
	String content=notice.getContent();
	
	
	String sql="INSERT INTO notice (title,writer_id,content) VALUES (?,?,?)";
	
	Class.forName("oracle.jdbc.driver.OracleDriver");
	Connection con=DriverManager.getConnection(url,user,PW);
	//Statement st=con.createStatement();
	//ResultSet rs=st.executeQuery(sql);
	PreparedStatement st=con.prepareStatement(sql);
	
	st.setString(1, Title);			
	st.setString(2, writer_id);			
	st.setString(3, content);			
	
	int result=st.executeUpdate();

	st.close();
	con.close();
	return result;
	
}

public int update(Notice notice) throws ClassNotFoundException, SQLException {
	
	String title=notice.getTitle();
	String writer_id=notice.getWriter_id();
	String content=notice.getContent();
	int id=notice.getID();
	
	String sql="update notice set title=? , writer_id=? , content=?" + 
			"where id=?";
	
	Class.forName("oracle.jdbc.driver.OracleDriver");
	Connection con=DriverManager.getConnection(url,user,PW);
	PreparedStatement st=con.prepareStatement(sql);
	st.setString(1, title);
	st.setString(2, writer_id);
	st.setString(3, content);
	st.setInt(4, id);
	
	int result=st.executeUpdate();
	
	st.close();
	con.close();
	return result;
}

public int delete(int id) throws ClassNotFoundException, SQLException {
	
	int  ID= id;

	String sql="DELETE * from notice where id=?";
	
	Class.forName("oracle.jdbc.driver.OracleDriver");
	Connection con=DriverManager.getConnection(url,user,PW);
	PreparedStatement st=con.prepareStatement(sql);
	st.setInt(1, id);
	
	int result=st.executeUpdate();
	
	
	st.close();
	con.close();
	
	return result;
}

}

이렇게 클래스를 완성했다. C R U D 는 메서드 별로 

C : insert()

R : getNoticeList()

U : Update()

D : delete()

이렇다. 

 

Retrieve 를 LIST로 구현한 이유가 궁금할 수도 있는데, 이것도 생각이 필요했다.

DB에서 테이블set을 가져오자? 어떤 형태로? 원래라면, resultset 형태로 가져온다.

그런데, JAVA에 resultset을 여러개 저장할 수 있는 자료구조가 존재하느냐? ->ArrayList가 적합하다.

그런데 resultset이라는 타입(자료형)이 없어서 만들어야한다. 무엇으로 -> NOTICE라는 클래스 타입을 따로 만들어서

그것을 resultset취급을 하자. 

이런 논리를 통해서 나는 NOTICE라는 타입을 만들어서 DB에서 가져온 resultset의 칼럼들을 Notice 의 인스턴스멤버로 받아왔다.

package com.newlecture.app.entity;

import java.sql.Date;

public class Notice {
	
	public Notice(int iD, String title, String writer_id, String content, java.sql.Date date, int hit, String files) {
		super();
		ID = iD;
		this.title = title;
		this.writer_id = writer_id;
		this.content = content;
		Date = date;
		this.hit = hit;
		Files = files;
	}
	private int ID ;
	private String title;
	private String writer_id;
	private String content;
	private Date Date;
	private int hit;
	private String Files;
	public int getID() {
		return ID;
	}
	public void setID(int iD) {
		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 getDate() {
		return Date;
	}
	public void setDate(Date date) {
		Date = date;
	}
	public int getHit() {
		return hit;
	}
	public void setHit(int hit) {
		this.hit = hit;
	}
	public String getFiles() {
		return Files;
	}
	public void setFiles(String files) {
		Files = files;
	}

}

 

2.가져온 데이터를 사용자들에게 보여줘야한다.  어떻게? 일단 웹페이지로 보여주기엔 능력이 모자르니 콘솔기반으로 보여주자 -> 사용자에게 가져온 데이터를 보여주고, CRUD기능의 입력을 담당하는 UI를 만들자 -> NOTICECONSOLE 클래스에 이 책임을 지우자.

import java.sql.SQLException;
import java.util.List;
import java.util.Scanner;

import com.newlecture.app.entity.Notice;
import com.newlecture.app.service.NoticeService;

public class NoticeConsole {
	
	private NoticeService service=new NoticeService();

	public void printConsole() throws ClassNotFoundException, SQLException {
		// TODO Auto-generated method stub
		List<Notice> list=service.getNoticeList();
		
			System.out.printf("--------------------------------\n");
			System.out.printf("<동훈이의 공지사항>\n");
			System.out.printf("--------------------------------\n");
	    for(Notice n:list) {
			System.out.printf("%d / %s / %s / %s / %d \n",
					n.getID(),n.getContent(),n.getWriter_id(),n.getDate(),n.getHit());
		}
	    System.out.printf("--------------------------------\n");
	    System.out.printf("              %d/%d pages\n", 1,3);
			
		
	}

	public int inputUserData() {
		// TODO Auto-generated method stub
		Scanner s =new Scanner(System.in);
		System.out.println("1.상세조회 / 2.이전 / 3.다음 / 4.글쓰기 >   ");
		int result=Integer.valueOf(s.nextLine());
		return result;
	
	}

}

3. 이제 CRUD 서비스를 구현하는데 필요한 클래스를 구현했으니, 해당 기능들을 총괄적으로 실행시키는 MAIN 프로그램을 만들자.(스프링으로치면 Assembler) ->

package com.newlecture.app.MainProgram;

import java.sql.SQLException;

import com.newlecture.app.console.NoticeConsole;

public class MainProgram {

	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		// TODO Auto-generated method stub
		NoticeConsole ui=new NoticeConsole();
		
		EXIT:
		while(true) {
		
	    ui.printConsole();
	    
	    
	    int menu=
	    		ui.inputUserData();
	    switch(menu) {
	    case 1:
	    	break;
	    case 2:
	    	break;
	    case 3:
	    	break;
	    case 4:
	    	break;
	    case 5:
	    	System.out.println("시스템을 종료합니다.");
	    	break EXIT;
	    }
		}
	}

}

메인 프로그램인 여기서, 모든 클래스를 실행한다.

 

나는 위와같은 논리적 구조로, CRUD 서비스를 구현할때, 필요한 기능을 클래스 별 책임으로 나누었다.

사실 구체적인 코드설명은 앞전 CRUD서비스를 한번 만들어 보면서 자세하게 했다. 

이번 포스팅의 의의는 ROWNUM을 이용한 페이징의  보완설명과, 내가 앞으로 백엔드 개발자를 하며 무지성으로 코드를 짜는것이 아니라, 이렇게, 역할과 책임에 따라 클래스를 나누고, 항상 생각하면서 만들어야 한다는 과정을 보여준 것이다.

처음으로, 이런 논리적 사고를 거쳐서 클래스를 만들고, 서비스를 만들어 보았는데, 재미있었기도 했고, 앞으로 내가 무수히 많은 코드를 짤 것인데 참고할만한 경험을 한거 같아서 뿌듯하다.

 

CRUD 게시판 서비스 최종 코드 파일.

1. 서비스 클래스(CRUD의 직접 구현 및, 글쓰기 기능추가.)

NoticeService.class
0.00MB

2. UI를 콘솔에 띄어주는 책임을 가진 Console클래스.

NoticeConsole.class
0.00MB

 

3.실제 클래스들을 실행시키는 메인 프로그램.

MainProgram.class
0.00MB

4.Notice 클래스 파일( DB에서 가져온 테이블을 Retrieve 단계에서 리스트에 넣을때, ResultSet 역할 사용)

NoticeConsole.class
0.00MB