[Java] JDBC ✨

soyeon·2022년 7월 21일
0

Java_카카오클라우드

목록 보기
14/15
post-thumbnail

JDBC(Java Database Connectivity)

: Java class와 interface의 집합

  • 기존의 문제점
    기존의 문제

: database마다 각기 구현이 다르기 때문에 직접적으로 접근하려면 각 database마다 코드가 달라지게 된다. -> 코드의 재활용성에 문제가 발생한다.
=> JDBC로 공통된 interface를 만들어 database의 종류가 달라도 동일하게 사용한다.

  • JDBC를 이용해 해결
    JDBC를 이용

MySQL에서 driver
MySQL에서 driver

: 프로젝트의 build path에 포함시켜서 사용하면 된다.

없는 경우 MySQL에 들어가서 다운 받는다.

프로젝트의 build path에 넣어준다.
1

2

JDBC 연결

: 설치한 JDBC driver를 우리 프로그램에서 사용할 수 있도록 만들어 주는 작업이다.

  1. JDBC driver loading 작업
    : 사용하려는 Database에 맞는 driver class를 등록한다.
    - Class라는 class를 이용한다.
    - DriverManager class

  2. DBMS와 연결
    : 실제로 Database와 연결한다. 만약 연결을 성공하면 "Connection" instance가 생성된다. -> 부하가 많이 걸린다. 다 사용한 후 Database의 session이 종료 될 수 있도록 반드시 자원을 해제(close)해 주어야 한다.
    "Jdbc URL"을 작성하고, id, password를 함께 보내서 connection을 생성한다.

  3. Statement를 생성

    Statement

    1. 일반 statement
    2. prepared statement (개량형)
    3. callable statement
  4. Query 실행
    : statement를 이용해서 SQL query를 DBMS에 전달해서 실행한다.
    😎실행 시키는 method
    - execute() : 다 된다. 대신 내가 나름대로 처리를 해야 한다.
    - executeQuery() : select 계열에서 사용한다.
    - executeUpdate() : insert, update, delete 계열에서 사용한다.
    => executeQuery()는 ResultSet을 리턴한다.
    => executeUpdate()는 int를 리턴한다. (영향을 받은 row의 수)

  5. 결과 처리
    ResultSet rs = pstmt.executeQuery();
    코드가 실행이 되면 쿼리의 결과가 전송이 된다. 그것을 rs에 저장한다. rs.next()로 받아온 결과 테이블에서 값이 없으면 false를 리턴한다.

  • 예제 코드
    : SELECT
package lecture0721;

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

public class Main {
	
	public static void main(String[] args) {
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			// 1. JDBC Driver Loading
			// MySQL 8.0부터는 아래의 class를 이용해요.
			Class.forName("com.mysql.cj.jdbc.Driver");
			System.out.println("드라이버 로딩 성공!");
			
			// 2. 데이터베이스 연결
			String jdbcURL = "jdbc:mysql://localhost:3306/sqldb?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false";
			con = DriverManager.getConnection(jdbcURL, "root", "password");
			System.out.println("데이터베이스 연결 성공!");
			
			String sql = "SELECT userID, name, addr FROM usertbl";
			// 3. Statement 생성			
			// PreparedStatement 생성
			pstmt = con.prepareStatement(sql);
			
			// 4. 실행
			rs = pstmt.executeQuery();
			
			// 5. 결과처리
			while(rs.next()) {
				String id = rs.getString(1);
				String name = rs.getString(2);
				String addr = rs.getString(3);
				System.out.println(id + ", " + name + ", " + addr);
			}
			
		} catch (ClassNotFoundException e1) {
			System.out.println(e1);
		} catch (SQLException e2) {
			System.out.println(e2);
		} finally {
			// 6. 사용한 자원을 해제해요.
			try {
				if(rs != null) rs.close();
				if(pstmt != null) pstmt.close();
				if(con != null) con.close();
			} catch (Exception e) {

			}
		}
	}
}

실행 결과 : 드라이버 로딩 성공!
		   데이터베이스 연결 성공!
		   BBK, 바비킴, 서울
		   EJW, 은지원, 경북
		   JKW, 조관우, 경기
		   JYP, 조용필, 경기
		   KBS, 김범수, 경남
		   KKH, 김경호, 전남
		   LJB, 임재범, 서울
		   LSG, 이승기, 서울
		   SSK, 성시경, 서울
		   YJS, 윤종신, 경남
  • 예제 코드
    : DELETE
package lecture0721;

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

public class Main {
	
	public static void main(String[] args) {
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			// 1. JDBC Driver Loading
			// MySQL 8.0부터는 아래의 class를 이용해요.
			Class.forName("com.mysql.cj.jdbc.Driver");
			System.out.println("드라이버 로딩 성공!");
			
			// 2. 데이터베이스 연결
			String jdbcURL = "jdbc:mysql://localhost:3306/sqldb?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false";
			con = DriverManager.getConnection(jdbcURL, "root", "password");
			System.out.println("데이터베이스 연결 성공!");
			
			// PreparedStatement는 IN Parameter를 사용할 수 있다.
			String sql = "DELETE FROM buytbl WHERE userID = ?";
			// 3. Statement 생성			
			// PreparedStatement 생성
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, "KBS");
			
			// 4. 실행
			int result = pstmt.executeUpdate();
			
			// 5. 결과처리
			System.out.println("총 " + result + "행이 삭제되었습니다.");
			
		} catch (ClassNotFoundException e1) {
			System.out.println(e1);
		} catch (SQLException e2) {
			System.out.println(e2);
		} finally {
			// 6. 사용한 자원을 해제해요.
			try {
				if(pstmt != null) pstmt.close();
				if(con != null) con.close();
			} catch (Exception e) {

			}
		}
	}
}

실행 결과 : 드라이버 로딩 성공!
		   데이터베이스 연결 성공!3행이 삭제되었습니다.
  • 예제 코드
    : DELETE (Transaction)
package lecture0721;

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

public class Main {
	
	public static void main(String[] args) {
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			// 1. JDBC Driver Loading
			// MySQL 8.0부터는 아래의 class를 이용해요.
			Class.forName("com.mysql.cj.jdbc.Driver");
			System.out.println("드라이버 로딩 성공!");
			
			// 2. 데이터베이스 연결
			String jdbcURL = "jdbc:mysql://localhost:3306/sqldb?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false";
			con = DriverManager.getConnection(jdbcURL, "root", "password");
			System.out.println("데이터베이스 연결 성공!");
			
			con.setAutoCommit(false);  // transaction 시작
			
			// PreparedStatement는 IN Parameter를 사용할 수 있다.
			String sql = "DELETE FROM buytbl WHERE userID = ?";
			// 3. Statement 생성			
			// PreparedStatement 생성
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, "BBK");
			
			// 4. 실행
			int result = pstmt.executeUpdate();
			
			// 5. 결과처리
			System.out.println("총 " + result + "행이 삭제되었습니다.");
			
			con.rollback();  // transaction이 종료
			
		} catch (ClassNotFoundException e1) {
			System.out.println(e1);
		} catch (SQLException e2) {
			System.out.println(e2);
		} finally {
			// 6. 사용한 자원을 해제해요.
			try {
				if(pstmt != null) pstmt.close();
				if(con != null) con.close();
			} catch (Exception e) {

			}
		}
	}
}

실행 결과 : 드라이버 로딩 성공!
		   데이터베이스 연결 성공!4행이 삭제되었습니다.

: rollback을 시켰기 때문에 database에서 확인하면 행이 삭제되어 있지 않다.

Connection pool

< 동시에 많은 사용자에 대해 Database 처리를 제공하려면? >

🤔그냥 connection을 공유하면 안될까?
-> 한 사람이 transaction을 설정해서 사용하고 있는데 다른 사람이 commit 시켜버리면 안된다. 따라서 한 사람당 하나의 connection을 부여해야 한다.

🤩pooling 기법을 사용해야 한다.
-> connection을 많이 만들어 놓은 뒤에 대여하고 반납하는 방식으로 동작한다.

  • 속도 향상
  • 자원의 효율성 향상
  • connection 수 제어

많은 사람들을 처리하기 위해서는 필수적으로 사용해야 한다.
직접 구현하지 않고, 만들어져 있는 connection pool을 가져다가 사용한다.
=> 무료(Apache commons DBCP)

Apache commons
DBCP, Pool, Logging을 download 받고, project의 build path에 추가해준다.

package lecture0721;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

public class MainDBCP {

	private static BasicDataSource basicDS;
	
	static {
		try {
			basicDS = new BasicDataSource();
			Properties properties = new Properties();
			
			InputStream is = new FileInputStream("resources/db.properties");
			properties.load(is);
			
			basicDS.setDriverClassName(properties.getProperty("DRIVER_CLASS"));
			basicDS.setUrl(properties.getProperty("JDBC_URL"));
			basicDS.setUsername(properties.getProperty("DB_USER"));
			basicDS.setPassword(properties.getProperty("DB_PASSWORD"));
			
			basicDS.setInitialSize(10);
			basicDS.setMaxTotal(10);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	public static DataSource getDataSource() {
		return basicDS;
	}
	
	public static void main(String[] args) {
		
		Connection con = null;
		DataSource ds = getDataSource();
		
		try {
			con = ds.getConnection();
			con.setAutoCommit(false);
			
			String sql = "DELETE FROM buytbl";
			PreparedStatement pstmt = con.prepareStatement(sql);
			
			int result = pstmt.executeUpdate();
			
			con.commit();
			
			con.close();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
}

코드로 연습해보기

keyword로 책 검색하고, ISBN으로 책 삭제하기

  • 내 코드
package lecture0721;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class BookFind extends Application{

	private static BasicDataSource basicDS;
	TextArea textarea;
	Button findBtn, deleteBtn;
	TextField textField;
	
	static {
		try {
			basicDS = new BasicDataSource();
			Properties properties = new Properties();
			
			InputStream is = new FileInputStream("resources/db.properties");
			properties.load(is);
			
			basicDS.setDriverClassName(properties.getProperty("DRIVER_CLASS"));
			basicDS.setUrl(properties.getProperty("JDBC_URL"));
			basicDS.setUsername(properties.getProperty("DB_USER"));
			basicDS.setPassword(properties.getProperty("DB_PASSWORD"));
			
			basicDS.setInitialSize(10);
			basicDS.setMaxTotal(10);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	public static DataSource getDataSource() {
		return basicDS;
	}
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		
		// 화면 구성
		BorderPane root = new BorderPane();
		root.setPrefSize(700, 500);  // window 크기
		DataSource ds = getDataSource();
		
		textarea = new TextArea();
		root.setCenter(textarea);  // 화면 center에 textarea를 붙인다.
		
		findBtn = new Button("Keyword로 검색");
		findBtn.setPrefSize(150, 40);
		findBtn.setOnAction(e -> {
			textarea.clear();
			Connection con = null;
			ResultSet rs = null;
			PreparedStatement pstmt = null;
			try {							
				con = ds.getConnection();
				con.setAutoCommit(false);

				String sql = "SELECT bisbn, btitle, bauthor FROM book WHERE btitle like '%" + textField.getText() + "%'";
				pstmt = con.prepareStatement(sql);
				
				rs = pstmt.executeQuery();
				
				while(rs.next()) {
					String bisbn = rs.getString(1);
					String btitle = rs.getString(2);
					String bauthor = rs.getString(3);
					textarea.appendText(btitle + "   " + bauthor + "   " + bisbn + "\n");
				}
				
				con.commit();				
			} catch (Exception e1) {
				// TODO: handle exception
			} finally {
				// 6. 사용한 자원을 해제해요.
				try {
					if(rs != null) rs.close();
					if(pstmt != null) pstmt.close();
					if(con != null) con.close();
				} catch (Exception e2) {

				}
			}			
		});
		
		textField = new TextField();
		textField.setPrefSize(350, 40);
		
		deleteBtn = new Button("ISBN으로 삭제");
		deleteBtn.setPrefSize(150, 40);
		deleteBtn.setOnAction(e -> {
			Connection con = null;
			PreparedStatement pstmt = null;		
			try {			
				con = ds.getConnection();
				con.setAutoCommit(false);

				String sql = "DELETE FROM book WHERE bisbn = ?";
				pstmt = con.prepareStatement(sql);
				pstmt.setString(1, textField.getText());
				
				int result = pstmt.executeUpdate();
				
				if (result > 0) {
					textarea.appendText("삭제 성공");
				}
				
				con.commit();
			} catch (Exception e1) {
				// TODO: handle exception
			} finally {
				// 6. 사용한 자원을 해제해요.
				try {
					if(pstmt != null) pstmt.close();
					if(con != null) con.close();
				} catch (Exception e2) {

				}
			}	
		});
	
		FlowPane flowPane = new FlowPane();
		flowPane.setPadding(new Insets(10,10,10,10));  // 여백을 준다.
		flowPane.setPrefSize(700, 40);
		flowPane.setHgap(10);
		flowPane.getChildren().add(findBtn);  // 버튼 부착
		flowPane.getChildren().add(textField);
		flowPane.getChildren().add(deleteBtn);
			
		root.setBottom(flowPane);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.show();
	}
	
	public static void main(String[] args) {
		launch();  // thread가 하나 만들어지면서 start를 실행하게 된다.
	}
}

실행 결과
실행 결과

-> 한 클래스에 코드를 모두 짜면 재사용이 어려워진다.

Layered Architecture

Layered Architecture

  • 바꾼 코드
    lecture0721.simplebooksearch => SimpleBookSearch class(View(JavaFX))
    lecture0721.simplebooksearch.service => BookService class(Service 역할) 조회, 삭제(하나의 transaction)
    lecture0721.simplebooksearch.vo => BookVO class(VO, DTO 역할)
    lecture0721.simplebooksearch.dao => BookDAO class(데이터베이스 처리)
package lecture0721.simplebooksearch;

import java.util.ArrayList;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import lecture0721.simplebooksearch.service.BookService;
import lecture0721.simplebooksearch.vo.BookVO;

public class SimpleBookSearch extends Application{
	
	TextArea textarea;
	Button findBtn, deleteBtn;
	TextField textField;
		
	@Override
	public void start(Stage primaryStage) throws Exception {
		
		// service 객체
		BookService service = new BookService();
		
		// 화면 구성
		BorderPane root = new BorderPane();
		root.setPrefSize(700, 500);  // window 크기
		
		
		textarea = new TextArea();
		root.setCenter(textarea);  // 화면 center에 textarea를 붙인다.
		
		findBtn = new Button("Keyword로 검색");
		findBtn.setPrefSize(150, 40);
		findBtn.setOnAction(e -> {
			textarea.clear();
			ArrayList<BookVO> result = service.bookSearchByKeyword(textField.getText());
			
			// result를 이용해서 화면에 출력
			for(BookVO book : result) {
				textarea.appendText(book.getBtitle() + "  " + book.getBauthor() + "  " + book.getBisbn() + "\n");
			}
		});
		
		textField = new TextField();
		textField.setPrefSize(350, 40);
		
		deleteBtn = new Button("ISBN으로 삭제");
		deleteBtn.setPrefSize(150, 40);
		deleteBtn.setOnAction(e -> {	
			int result = service.bookDeleteByISBN(textField.getText());
			if(result > 0) {
				textarea.appendText("삭제 성공!");
			}
		});
	
		FlowPane flowPane = new FlowPane();
		flowPane.setPadding(new Insets(10,10,10,10));  // 여백을 준다.
		flowPane.setPrefSize(700, 40);
		flowPane.setHgap(10);
		flowPane.getChildren().add(findBtn);  // 버튼 부착
		flowPane.getChildren().add(textField);
		flowPane.getChildren().add(deleteBtn);
			
		root.setBottom(flowPane);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.show();
	}
	
	public static void main(String[] args) {
		launch();  // thread가 하나 만들어지면서 start를 실행하게 된다.
	}
}

package lecture0721.simplebooksearch.service;

import java.util.ArrayList;

import lecture0721.simplebooksearch.dao.BookDAO;
import lecture0721.simplebooksearch.vo.BookVO;

public class BookService {
	
	private BookDAO dao;
	
	public BookService() {
		this.dao = new BookDAO();
	}
	
	public ArrayList<BookVO> bookSearchByKeyword(String keyword) {
		// 키워드를 이용해서 책을 찾는 로직 처리
		// for, if 로직처리가 일반적으로 나오는데
		// Database 처리를 해야 해요
		ArrayList<BookVO> result = dao.select(keyword);
		return result;
	}
	
	public int bookDeleteByISBN(String bisbn) {
		// 책 고유번호를 받아서 해당 책을 삭제하는 로직 처리
		int result = dao.delete(bisbn);
		return result;
	}
}

package lecture0721.simplebooksearch.vo;

public class BookVO {
	
	private String bisbn;
	private String btitle;
	private String bdate;
	private int bpage;
	private int bprice;
	private String bauthor;
	
	public BookVO() {
		
	}

	public String getBisbn() {
		return bisbn;
	}

	public void setBisbn(String bisbn) {
		this.bisbn = bisbn;
	}

	public String getBtitle() {
		return btitle;
	}

	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}

	public String getBdate() {
		return bdate;
	}

	public void setBdate(String bdate) {
		this.bdate = bdate;
	}

	public int getBpage() {
		return bpage;
	}

	public void setBpage(int bpage) {
		this.bpage = bpage;
	}

	public int getBprice() {
		return bprice;
	}

	public void setBprice(int bprice) {
		this.bprice = bprice;
	}

	public String getBauthor() {
		return bauthor;
	}

	public void setBauthor(String bauthor) {
		this.bauthor = bauthor;
	}
}

package lecture0721.simplebooksearch.dao;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

import lecture0721.simplebooksearch.vo.BookVO;

public class BookDAO {

	private static BasicDataSource basicDS;
	
	public BookDAO() {
		try {
			basicDS = new BasicDataSource();
			Properties properties = new Properties();
			
			InputStream is = new FileInputStream("resources/db.properties");
			properties.load(is);
			
			basicDS.setDriverClassName(properties.getProperty("DRIVER_CLASS"));
			basicDS.setUrl(properties.getProperty("JDBC_URL"));
			basicDS.setUsername(properties.getProperty("DB_USER"));
			basicDS.setPassword(properties.getProperty("DB_PASSWORD"));
			
			basicDS.setInitialSize(10);
			basicDS.setMaxTotal(10);
			
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	public static DataSource getDataSource() {
		return basicDS;
	}
	
	public ArrayList<BookVO> select(String keyword) {
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		ArrayList<BookVO> list = null;
		
		DataSource ds = getDataSource();
		
		try {							
			con = ds.getConnection();

			String sql = "SELECT bisbn, btitle, bauthor FROM book WHERE btitle like '%" + keyword + "%'";
			pstmt = con.prepareStatement(sql);
			
			rs = pstmt.executeQuery();
			
			list = new ArrayList<BookVO>();
			while(rs.next()) {
				BookVO tmp = new BookVO();
				
				tmp.setBtitle(rs.getString("btitle"));
				tmp.setBauthor(rs.getString("bauthor"));
				tmp.setBisbn(rs.getString("bisbn"));
				
				list.add(tmp);
			}			
		} catch (Exception e1) {
			
		} finally {
			try {
				if(rs != null) rs.close();
				if(pstmt != null) pstmt.close();
				if(con != null) con.close();
			} catch (Exception e2) {

			}
		}
		return list;		
	}
	
	public int delete(String bisbn) {
		Connection con = null;
		PreparedStatement pstmt = null;
		int result = 0;
		
		DataSource ds = getDataSource();
		
		try {			
			con = ds.getConnection();

			String sql = "DELETE FROM book WHERE bisbn = ?";
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, bisbn);
			
			result = pstmt.executeUpdate();

		} catch (Exception e1) {
			// TODO: handle exception
		} finally {
			// 6. 사용한 자원을 해제해요.
			try {
				if(pstmt != null) pstmt.close();
				if(con != null) con.close();
			} catch (Exception e2) {

			}
		}
		return result;
	}
}

0개의 댓글