Bigfat

[Web] 싱글톤 패턴을 이용하여 데이터베이스 접근하기 (게시판 구현) 본문

Java/web

[Web] 싱글톤 패턴을 이용하여 데이터베이스 접근하기 (게시판 구현)

kyou 2017. 4. 4. 21:28

싱글톤 패턴(Singleton pattern)을 이용하여 레코드를 삽입, 조회해보자

  게시판 구현에 필요한 오라클 데이터베이스 접근을 싱글톤 패턴을 이용해 구현해보자. 싱글톤 패턴은 동일한 자원이 불필요하게 여러 개 만들어질 필요가 없을 때 주로 사용한다.


  우선 게시글에 대한 테이블, 시퀀스를 생성하자.

--게시판(Bulletin Board System) 테이블 생성
CREATE TABLE BBS(
    ARTICLE_NUMBER NUMBER PRIMARY KEY,
    ID VARCHAR2(15) NOT NULL,
    TITLE VARCHAR2(15) NOT NULL,
    CONTENT CLOB NOT NULL,
    GROUP_ID NUMBER NOT NULL,
    DEPTH NUMBER NOT NULL,
    POS NUMBER NOT NULL,    --position, 게시글 순서를 조정
    HIT NUMBER NOT NULL,    --조회수
    WRITE_DATE DATE NOT NULL,
    FILE_NAME VARCHAR2(300)
);

--시퀀스 생성
CREATE SEQUENCE BBS_SEQ
 START WITH 1
INCREMENT BY 1;


  이제 계층 간의 데이터 교환을 위해 DTO(Data Transfer Object, 또는 VO, Value Object) 클래스를 만들자. DTO클래스의 멤버변수들은 private으로 제한하여 메서드를 통해서만 접근할 수 있도록 하였다. alt+shift+S를 눌러 Generate Getters and Setters와 Generate toString()을 선택해 메서드를 편리하게 생성할 수 있다.

package com.edu.bbs;

import java.sql.Timestamp;

public class BBSDto {
	
	private int articleNumber;
	private String id;
	private String title;
	private String content;
	private int groupId;
	private int depth;
	private int pos;
	private int hit;
	private Timestamp writeDate;    // 추후 String으로 변경
	private String fileName;
	
	public int getArticleNumber() {
		return articleNumber;
	}
	public void setArticleNumber(int articleNumber) {
		this.articleNumber = articleNumber;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public int getGroupId() {
		return groupId;
	}
	public void setGroupId(int groupId) {
		this.groupId = groupId;
	}
	public int getDepth() {
		return depth;
	}
	public void setDepth(int depth) {
		this.depth = depth;
	}
	public int getPos() {
		return pos;
	}
	public void setPos(int pos) {
		this.pos = pos;
	}
	public int getHit() {
		return hit;
	}
	public void setHit(int hit) {
		this.hit = hit;
	}
	public Timestamp getWriteDate() {
		return writeDate;
	}
	public void setWriteDate(Timestamp writeDate) {
		this.writeDate = writeDate;
	}
	public String getFileName() {
		return fileName;
	}
	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
	
	@Override
	public String toString() {
		return "BBSDto [articleNumber=" + articleNumber + ", id=" + id + ", title=" + title + ", content=" + content
				+ ", groupId=" + groupId + ", depth=" + depth + ", pos=" + pos + ", hit=" + hit + ", writeDate="
				+ writeDate + ", fileName=" + fileName + "]";
	}
	
}


  오라클 데이터베이스 커넥션을 구현한 클래스다. 오라클 외에도 다른 DB를 사용할 수 있기 때문에 따로 클래스화하자. 싱글톤 패턴은 코드 내에 주석을 참고하자. getConnection메서드의 파라미터들은 각자 데이터베이스 접속이름, 비밀번호에 맞게 변경해주자.

package com.edu.bbs;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * OracleDBConnector.java
 * Oracle Connection을 구현한 클래스
 * 여러 데이터베이스 사용을 위해 커넥션을 클래스로 분리한다.
 */
public class OracleDBConnector {
//	private static OracleDBConnector orclDbc = new OracleDBConnector();		// 1.바로 메모리 할당하는 방법
	private static OracleDBConnector orclDbc;

	// 외부에서의 인스턴스화를 막는다.
	private OracleDBConnector() {}

	// OracleDBConnector 인스턴스를 얻는 방법은 getInstance()메서드로 호출하는 방법뿐이다.
	public static OracleDBConnector getInstacne() {
		if (orclDbc == null) {
			orclDbc = new OracleDBConnector();		// 2.메모리에 할당되지 않았을 때 할당하는 방법
		}
		return orclDbc;
	}

	public Connection getConnection() throws ClassNotFoundException, SQLException {
		Class.forName("oracle.jdbc.driver.OracleDriver");
		// core.log를 사용하게되면 쿼리 결과를 콘솔에 찍을 수 있다.
//		Class.forName("core.log.jdbc.driver.OracleDriver");
		String url = "jdbc:oracle:thin:@localhost:1521:XE";
		Connection conn = DriverManager.getConnection(url, "Kyou", "1234");
		return conn;
	}
}


  DB에서 조회, 삽입 등 데이터를 조작하는 DAO(Data Access Object)클래스를 구현한다. insertArticle()메서드는 게시글 하나를 삽입하고, selectArticle()메서드는 게시글 하나를 조회하는 기능을 한다.

package com.edu.bbs;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

/**
 * BBSOracleDao.java
 * DB를 사용해 데이터를 조회하거나 조작하는 기능을 구현한 클래스
 * 일반, 공지게시판 등 재사용성을 늘리기 위해 클래스로 분리한다.
 */
public class BBSOracleDao {
	private static BBSOracleDao bbsOracleDao;
	private OracleDBConnector orclDbc = OracleDBConnector.getInstacne();
	
	Connection conn;
	PreparedStatement pstmt;
	ResultSet rs;
	StringBuffer query;
	
	BBSDto article;
	ArrayList<BBSDto> articles;

	private BBSOracleDao() {}
	
	public static BBSOracleDao getInstance() {
		if(bbsOracleDao == null) {
			bbsOracleDao = new BBSOracleDao();
		}
		return bbsOracleDao;
	}
	
	// synchronized, 한 명의 글쓰기를 처리한 후 다른 사람의 글쓰기를 처리해야 한다.
	public synchronized int insertArticle(BBSDto article) throws ClassNotFoundException, SQLException {
		conn = orclDbc.getConnection();
		query = new StringBuffer();
		query.append("INSERT INTO bbs ");
		query.append("VALUES(bbs_seq.nextval, ?, ?, ?, bbs_seq.currval, 0, 0, 0, sysdate, ?)");
		pstmt = conn.prepareStatement(query.toString());
		// parameterIndex는 쿼리문의 ? 순서대로 적어주며, 1부터 시작한다.
		pstmt.setString(1, article.getId());
		pstmt.setString(2, article.getTitle());
		pstmt.setString(3, article.getContent());
		pstmt.setString(4, article.getFileName());
		
		int result = pstmt.executeUpdate();
		
		disconnect();
		
		return result;
	}
	
	public BBSDto selectArticle(String articleNumber) throws ClassNotFoundException, SQLException {
		conn = orclDbc.getConnection();
		query = new StringBuffer();
		query.append("SELECT * FROM bbs WHERE article_number = ?");
		pstmt = conn.prepareStatement(query.toString());
		pstmt.setString(1, articleNumber);
		rs = pstmt.executeQuery();
		
		if(rs.next()) {
			article = new BBSDto();
			article.setArticleNumber(rs.getInt("article_number"));
			article.setId(rs.getString("id"));
			article.setTitle(rs.getString("title"));			
			article.setDepth(rs.getInt("depth"));			
			article.setContent(rs.getString("content"));
			article.setHit(rs.getInt("hit"));
			article.setGroupId(rs.getInt("group_id"));
			article.setPos(rs.getInt("pos"));
			article.setWriteDate(rs.getTimestamp("write_date"));
			article.setFileName(rs.getString("file_name"));
		}
		
		disconnect();
		
		return article;
	}
	
	public void disconnect() throws SQLException {
		if(rs != null) {
			rs.close();
		}
		pstmt.close();
		conn.close();
	}
}


  main메서드에서 게시글을 삽입하고 첫 번째 게시글을 조회하여 콘솔에 보이도록 해보자. ARTICLE_NUMBER 컬럼의 데이터 타입이 NUMBER인데도 파라미터를 String으로 던져주고 있다. 오라클11g(10부터였나)는 내부적으로 숫자형 문자를 숫자로 변환해주기 때문에 가능하다.

package com.edu.bbs;

public class DBTest {

	public static void main(String[] args) {
		BBSDto article = new BBSDto();
		article.setId("kim");
		article.setTitle("첫 번째 게시글 제목");
		article.setContent("첫 번째 게시글 내용");
		
		try {
			 if(BBSOracleDao.getInstance().insertArticle(article) == 1) {
				 System.out.println("레코드 삽입이 성공하였습니다.");
			 } else {
				 System.out.println("레코드 삽입이 실패하였습니다.");
			 }
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		try {
			System.out.println(BBSOracleDao.getInstance().selectArticle("1").toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}



[쉽게 배우는 소프트웨어 공학 singleton 패턴 참고]

[DAO / VO / DTO 란? 참고]