[JDBC] INSERT, UPDATE, DELETE 사용법과 CRUD Service 생성 예제

JDBC 이용한 데이터베이스 조작 방법

JDBC를 이용하면 Java 코드 내에서 자유롭게 데이터베이스를 조작할 수 있다.

서버로 전달된 요청이 데이터베이스 조회, 수정 등의 작업을 필요로 할 때 활용 할 수 있다.

이번 포스트에서는 INSERTUPDATE 활용법을 간단하게 정리해보고 실습 예제를 통해 이해를 돕는다.

INSERT 쿼리를 사용한 데이터 입력

아래 간단한 예시들은 모두 오라클 데이터베이스 기본 문서를 참조했다.

오라클 데이터베이스의 INSERT 쿼리는 테이블 안으로 데이터를 삽입하기 위해 INTO 와 함께 사용된다.

INSERT INTO departments
   VALUES (280, 'Recreation', 121, 1700);

위와 같이 INTO 뒤에는 데이터가 삽입될 테이블명을 적어준다. 이 방법은 모든 테이블 컬럼명과 순서를 알아야 한다는 단점이 있다. 그래서 보통은 아래 방법을 선호하는 것 같다.

INSERT INTO employees (employee_id, last_name, email, 
      hire_date, job_id, salary, commission_pct) 
   VALUES (207, 'Gregory', 'pgregory@oracle.com', 
      sysdate, 'PU_CLERK', 1.2E3, NULL);

위 예시는 employees 테이블이 가진 컬럼명을 기재하고 그 순서에 맞게 데이터를 삽입하는 모습을 보여준다. 이때, 테이블 고유 컬럼들의 순서는 신경쓰지 않아도 괜찮으나 괄호 안에 기재한 컬럼의 순서와는 동일한 자료형을 VALUES 이후에 입력해줘야 한다.

공식 문서는 이 밖에도 생소하지만 유용한 많은 삽입 예시들을 보여주는데, 조금 더 높은 레벨의 개발자를 희망한다면 숙지해야 할 사항이라 생각한다.

위에서 배운 사항을 JDBC에 적용해보자.

Statement 클래스는 SQL 구문이 정확하게 구성될 수 있게 도와주는 도구모음 이라고 생각하자.

이번에 사용해 볼 PreparedStatement 클래스는 문자열로의 매개변수 삽입 관련 작업에 특화된 도구모음이다.

선언 방법은 아래와 같다.

PreparedStatement st = con.prepareStatement(sql);

이번 포스트에서 사용할 INSERT, UPDATE 질의문은 조회문과 다르게 결과 집합을 반환받지 않는다. 이렇게 결과 집합을 반환하지 않는 질의문은 executeUpdate() 를 사용하며, 이때는 업데이트된 행 개수를 반환해준다.

아래 예시 코드에서는 다음과 같은 사항을 확인할 수 있다.

  • String sql 문자열 내부 인자가 삽입될 자리를 ?로 처리한 점
  • PreparedStatement st 클래스를 생성하기 위해 ? 문자를 포함한 문자열을 인자로 넘겨준 점
  • st.setString(index, value) 를 통해 st 클래스 내부 문자열의 ? 값을 바꿔주는 작업
  • st.setString(index, value) 에서 index는 1번부터 시작하는 점
  • st.executeUpdate() 작업은 int 형을 반환하는 점
  • st.execute(쿼리 문자열) 과는 다르게 기존 클래스 내부에 세팅된 문자열이 있어 인자로 아무것도 넣어주지 않은 점
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;

public class Program2 {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
		String sql = "INSERT INTO notice ( "
				+ "    title, "
				+ "    writer_id, "
				+ "    content, "
				+ "    files "
				+ ") VALUES (?,?,?,?)";
		
		String title = "Insert Test";
		String writer_id = "user_id";
		String content = "contents!";
		String files = "file";
		
		Class.forName("oracle.jdbc.driver.OracleDriver");
		Connection con = DriverManager.getConnection(url, "user_id", "password");
		
		// PrepareStatement (실행하기 전 문장을 채워주는 도구)
		PreparedStatement st = con.prepareStatement(sql);
		
		// 전달할 SQL의 N 번째에 어떤 값을 채울 것인지 설정 (INDEX는 1부터 시작)
		st.setString(1, title);
		st.setString(2, writer_id);
		st.setString(3, content);
		st.setString(4, files);
		
		// 저장된 SQL 실행. 이때, 절대 인자로 SQL을 전달하지 않는다. 이미 Statement에 저장되어 있음
		// 영향받은 행 개수를 반환해준다
		int result = st.executeUpdate();
		System.out.println(result);
		
		st.close();
		con.close();
	}
}

UPDATE 쿼리를 이용한 데이터 수정

데이터 수정 작업 역시 INSERT문과 동일하게 ? 을 통해 변수가 삽입될 부분을 설정하고 PreparedStatement 도구를 이용해 SQL 문장을 만든다.

앞에서는 INSERT 다음 곧바로 INTO 가 나왔지만 UPDATE 는 수정할 테이블 명이 나온 뒤 SET 옵션을 붙여준다는 차이점이 있다.

아래는 공식 문서에서 제공하는 간단한 예시다.

UPDATE employees
   SET commission_pct = NULL
   WHERE job_id = 'SH_CLERK';

아래 예시에서 역시 위에서 확인했던 것과 비슷한 사항들을 확인할 수 있다.

  • String sql 문자열 내부 인자가 삽입될 자리를 ?로 처리한 점
  • PreparedStatement st 클래스를 생성하기 위해 ? 문자를 포함한 문자열을 인자로 넘겨준 점
  • st.setString(index, value) 를 통해 st 클래스 내부 문자열의 ? 값을 바꿔주는 작업
  • st.setString(index, value) 에서 index는 1번부터 시작하는 점
  • 정수형 값을 설정할 때는 st.setInt(index, value) 를 사용한다는 점
  • st.executeUpdate() 작업은 int 형을 반환하는 점
  • st.execute(쿼리 문자열) 과는 다르게 기존 클래스 내부에 세팅된 문자열이 있어 인자로 아무것도 넣어주지 않은 점
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;

public class Program3 {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
		String sql = "UPDATE NOTICE SET TITLE=?, CONTENT=?, FILES=? WHERE ID=?";
		
		String title = "UPDATE TEST";
		String content = "UPDATED CONENTS!";
		String files = "FILES";
		int id = 21;
		
		Class.forName("oracle.jdbc.driver.OracleDriver");
		Connection con = DriverManager.getConnection(url, "user_id", "password");
		
		// PrepareStatement (실행하기 전 문장을 채워주는 도구)
		PreparedStatement st = con.prepareStatement(sql);
		
		// 전달할 SQL의 N 번째에 어떤 값을 채울 것인지 설정 (INDEX는 1부터 시작)
		st.setString(1, title);
		st.setString(2, content);
		st.setString(3, files);
		st.setInt(4, id);
		
		// 저장된 SQL 실행. 이때, 절대 인자로 SQL을 전달하지 않는다. 이미 Statement에 저장되어 있음
		// 영향받은 행 개수를 반환해준다
		int result = st.executeUpdate();
		System.out.println(result);
		
		st.close();
		con.close();
	}
}

DELETE 쿼리를 이용한 데이터 삭제

데이터 삭제 역시 간단하다. SQL 문장과 그에 따른 Setting 값만 바꿔주면 원하는대로 수행된다.

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;

public class Program4 {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
		String sql = "DELETE NOTICE ID=?";
		
		int id = 21;
		
		Class.forName("oracle.jdbc.driver.OracleDriver");
		Connection con = DriverManager.getConnection(url, "user_id", "password");
		
		// PrepareStatement (실행하기 전 문장을 채워주는 도구)
		PreparedStatement st = con.prepareStatement(sql);
		
		// 전달할 SQL의 N 번째에 어떤 값을 채울 것인지 설정 (INDEX는 1부터 시작)
		st.setInt(1, id);
		
		// 저장된 SQL 실행. 이때, 절대 인자로 SQL을 전달하지 않는다. 이미 Statement에 저장되어 있음
		// 영향받은 행 개수를 반환해준다
		int result = st.executeUpdate();
		System.out.println(result);
		
		st.close();
		con.close();
	}
}

CRUD Service 생성

지금까지 JDBC를 이용한 데이터베이스 테이블 삽입, 삭제, 수정 예제를 다뤘다. 각각은 서로 다른 자바 코드에서 동작한다. 이 네 가지 기능을 한 곳에 모아 관리 및 재사용 할 수 있을까?

이렇게 CRUD 기능을 수행하는 객체를 서비스 객체라고 말하며, 아래 코드들은 실험해보기 위한 예제 소스코드이다. 더 자세히 알아보고 싶은 사람은 유튜브에 newlec jdbc 를 검색해서 보면 더 이해가 빠를지도.

먼저 예제에서 사용할 데이터 엔티티인 공지사항(Notice) 를 아래와 같이 클래스로 생성한다. 엔티티란 직관적으로 이해하자면 데이터베이스 스키마 안의 테이블 하나라고 생각하면 편하겠다. (물론 모든 테이블이 엔티티는 아님)

// Notice.java
package com.pangtudy.app.entity;

import java.util.Date;

public class Notice {
	private int id;
	private String title;
	private String writeId;
	private Date regDate;
	private String content;
	private int hit;
	private String files;
	
	// 생성자
	public Notice() {
		
	}
	public Notice(int id, String title, String writeId, Date regDate, String content, int hit, String files) {
		this.id = id;
		this.title = title;
		this.writeId = writeId;
		this.regDate = regDate;
		this.content = content;
		this.hit = hit;
		this.files = files;
	}

	// Getter & Setter
	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 getWriteId() {
		return writeId;
	}
	public void setWriteId(String writeId) {
		this.writeId = writeId;
	}
	public Date getRegDate() {
		return regDate;
	}
	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public int getHit() {
		return hit;
	}
	public void setHit(int hit) {
		this.hit = hit;
	}
	public String getFiles() {
		return files;
	}
	public void setFiles(String files) {
		this.files = files;
	}	
}

GETTER, SETTER 생성 메소드를 하나하나 작성하기 보다는 이클립스에서 지원하는 도구를 활용하는 것이 편하다. 우클릭만 잘 써도 코딩이 쉬워진다.

메서드를 적어놓은 뒤 우클릭으로 구현을 클릭하면 해당 클래스 내부에 같은 이름의 메소드를 생성해주기도 한다.

다음으로는 NoticeService 클래스를 생성한다. url, uid, password, driver 와 같이 메서드에서 공통적으로 사용되는 문자열을 한 곳에 모아 private 으로 묶었다. 이러면 추후 수정할 때 한 곳만 고치면 되기에 유지 및 보수가 쉬워진다.

package com.pangtudy.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.pangtudy.app.entity.Notice;

public class NoticeService {
	private String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
	private String uid = "user_id";
	private String password = "password";
	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, password);
		Statement st = con.createStatement();
		ResultSet rs = st.executeQuery(sql);
		
		List<Notice> noticeList = new ArrayList<Notice>();
		while(rs.next()) {
			int id = rs.getInt("ID");
			String title = rs.getString("TITLE");
			String writerId = rs.getString("WRITER_ID");
			Date regDate = rs.getDate("REGDATE");
			String content = rs.getString("CONTENT");
			int hit = rs.getInt("HIT");
			String files = rs.getString("FILES");
			
			Notice notice= new Notice(id, title, writerId, regDate, content, hit, files);
			noticeList.add(notice);
		}
		
		rs.close();
		st.close();
		con.close();
		
		return noticeList;
	}

	public int insert(Notice notice) throws ClassNotFoundException, SQLException {
		String sql = "INSERT INTO notice ( "
				+ "    title, "
				+ "    writer_id, "
				+ "    content, "
				+ "    files "
				+ ") VALUES (?,?,?,?)";
		
		String title = notice.getTitle();
		String writer_id = notice.getWriteId();
		String content = notice.getContent();
		String files = notice.getFiles();
		
		Class.forName(driver);
		Connection con = DriverManager.getConnection(url, uid, password);
		
		PreparedStatement st = con.prepareStatement(sql);
		
		st.setString(1, title);
		st.setString(2, writer_id);
		st.setString(3, content);
		st.setString(4, files);
		
		int result = st.executeUpdate();
		
		st.close();
		con.close();
		
		return result;
	}
	
	public int update(Notice notice) throws ClassNotFoundException, SQLException {
		String sql = "UPDATE NOTICE SET TITLE=?, CONTENT=?, FILES=? WHERE ID=?";
		
		String title = notice.getTitle();
		String content = notice.getContent();
		String files = notice.getFiles();
		int id = notice.getId();
		
		Class.forName(driver);
		Connection con = DriverManager.getConnection(url, uid, password);
		
		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 ID=?";
		
		Class.forName(driver);
		Connection con = DriverManager.getConnection(url, uid, password);
		
		PreparedStatement st = con.prepareStatement(sql);
		
		st.setInt(1, id);
		
		int result = st.executeUpdate();
		
		st.close();
		con.close();
		
		return result;
	}
}

Updated:

Leave a comment