no image
[Python] 파이썬 기본적인 용어
키워드(keyword) 파이썬이라는 프로그래밍 언어가 만들어질 때, 이런 단어들은 파이썬 자체적으로 사용하겠다고 예약해 놓은 단어들 (총 35개) 키워드로 지정된 단어들은 변수명 같이 나중에 사용자가 마음대로 정할 수 있는 이름들로는 사용될 수 없기 때문에 알고 있어야 함 식별자(identifier) 키워드 외에 프로그램에서 사용자가 필요에 따라 이름을 만들어 사용하는 단어 즉, 변수, 상수, 함수, 클래스 등의 구분을 위해 사용하는 이름 식별자를 작성하는데 정답은 없지만 대부분의 경우 많은 프로그래머들이 관례적으로 사용하는 카멜 기법과 파스칼 표기법을 적절하게 조합 • 변수명이나 함수명은 카멜 표기법 • 클래스명은 파스칼 표기법 식별자 작성 규칙 식별자로 쓸 수 있는 문자 • 영문 대/소문자(A~Z, ..
2023.08.23
no image
[Java] next()와 nextLine()의 차이
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int number; String string; System.out.print("숫자 입력 : "); number = sc.nextInt(); System.out.println("문자열 입력 : "); string = sc.nextLine(); System.out.println("숫자 입력 결과 : " + number); System.out.println("문자열 입력 결과 : " + string); } } /* 숫자 입력 : 6546201 문자열 입력 : 숫자 입력 결과 : 65462..
2023.08.22
no image
[Java] DB와 연동한 게시판 구현
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. USER 테이블 Oracle MySQL BOARDS 테이블 Oracle MySQL 구현 Oracle Board.java import java.util.Date; public class Board { private int bno; private String btitle; private String bcontent; private String bwriter; private Date bdate; public int getBno() { return bno; } public void setBno(int bno) { this.bno = bno; } public String getBtitle() ..
2023.08.21
no image
[Java] DB 트랜잭션 처리
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 트랜잭션 트랜잭션(transaction)은 기능 처리의 최소 단위를 말한다. 하나의 기능은 여러가지 소작업들로 구성된다. 최소 단위라는 것은 이 소작업들을 분리할 수 없으며, 전체를 하나로 본다는 개념이다. 트랜잭션은 소작업들이 모두 성공하거나 실패해야 한다. 예를 들어 계좌 이체는 출금과 입금으로 구성된 트랜잭션이다. 출금과 입금 작업 중 하나만 성공할 수 없으며, 모두 성공하거나 모두 실패해야 한다. 계좌 이체는 DB 입장에서 보면 두 개의 계좌 금액을 수정하는 작업이다. 출금 계좌에서 금액을 감소시키고, 입금 계좌에서 금액을 증가시킨다. 따라서 아래와 같이 두 개의 UPDATE ..
2023.08.20
no image
[Java] DB 프로시저와 함수 호출
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 프로시저와 함수 이 글에서는 Oracle DB에 적용되는 프로시저와 함수를 다룬다. 클라이언트 프로그램(DB가 서버이고, DB를 이용하는 프로그램이 클라이언트)에서 매개값과 함께 프로시저 또는 함수를 호출하면 DB 내부에서 일련의 SQL문을 실행하고, 실행 결과를 클라이언트 프로그램으로 돌려주는 역할을 한다. 즉, 프로그램 내부에서 SQL문의 작업내용이 처리되지 않고 DB에서 처리가 된다. 클라이언트에서 처리하기 부담스러운 처리를 서버로 돌릴 수 있게 된다. 프로시저 : 리턴하는 값이 있지만, 주로 리턴하지 않고 작업 처리를 할 때 사용 예를 들어, 데이터 삽입, 삭제, 데이터 확인 ..
2023.08.19
no image
[Java] 데이터베이스에 저장(쓰기, 수정, 삭제) 및 읽기
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 데이터 베이스 구성 Account Table Boards Users 데이터 저장 데이터 쓰기(INSERT문) User 테이블 users 테이블에 새로운 사용자 정보를 저장하는 INSERT 문은 아래와 같다. INSERT INTO users (userid, username, userpassword, userage, useremail) VALUES ('winter', '한겨울', '12345', '25', 'winter@mycompany.com') 값을 ?로 대체한 매개변수화된 INSERT 문으로 변경하면 아래와 같다. INSERT INTO users (userid, username, u..
2023.08.18
no image
[Java] JDBC 개요 및 DB 연결하기
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. JDBC 자바는 데이터베이스와 연결해서 데이터 입출력 작업을 할 수 있도록 JDBC(Java Database Connectivity) 라이브러리 (java.sql 패키지)를 제공한다. JDBC는 데이터베이스 관리시스템(DBMS)의 종류와 상관없이 동일하게 사용할 수 있는 클래스와 인터페이스로 구성되어 있다. JDBC는 인터페이스다. 즉 구현 객체가 있어야 사용할 수 있다. 구현 객체는 JDBC Driver인데, 해당 구현 객체는 DBMS 제조사가 제공한다. JDBC Driver는 DBMS 마다 별도로 다운로드받아 사용해야 한다. 위 그림은 상속 관계를 나타낸 그림이 아니다. Drive..
2023.08.17
no image
[Java] TCP 채팅 프로그램
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. TCP 채팅 프로그램 작동 원리 클래스 용도 ChatServer -채팅 서버 실행 클래스 -ServerSocket을 생성하고 50001 포트에 바인딩 -ChatClient 연결 수락 후 SocketClient 생성 SocketClient -ChatClient와 1:1로 통신 ChatClient -채팅 클라이언트 실행 클래스 -ChatServer에 연결 요청 -SocketClient와 1:1로 통신 ChatServer 클래스에는 ServerSocket 객체가 50001번 포트로 들어오는 클라이언트 연결 요청을 수락한다. SocketClient 객체는 연결 요청한 클라이언트 수만큼 생성된..
2023.08.16

키워드(keyword)

  • 파이썬이라는 프로그래밍 언어가 만들어질 때, 이런 단어들은 파이썬 자체적으로
    사용하겠다고 예약해 놓은 단어들 (총 35개)
  • 키워드로 지정된 단어들은 변수명 같이 나중에 사용자가 마음대로 정할 수 있는
    이름들로는 사용될 수 없기 때문에 알고 있어야 함

 

식별자(identifier)

  • 키워드 외에 프로그램에서 사용자가 필요에 따라 이름을 만들어 사용하는 단어
  • 즉, 변수, 상수, 함수, 클래스 등의 구분을 위해 사용하는 이름
  • 식별자를 작성하는데 정답은 없지만 대부분의 경우 많은 프로그래머들이 관례적으로 사용하는 카멜 기법과 파스칼 표기법을 적절하게 조합
    • 변수명이나 함수명은 카멜 표기법
    • 클래스명은 파스칼 표기법

 

식별자 작성 규칙
식별자로 쓸 수 있는 문자
• 영문 대/소문자(A~Z, a~z), 숫자(0~9), 특수문자 중 언더 바(_)만 사용될 수 있음
• 영어 알파벳 외의 문자도 가능은 하지만 관례적으로 쓰지 않음

식별자로 쓸 수 없는 문자
• 키워드를 사용하면 안됨 (예 : if)
• 숫자로 시작하는 문자열 (예 : 0a)
• 특수문자가 포함된 문자열 (예 : a!)
• 공백이 포함된 문자열 (예 : a b)

 

 

들여쓰기(indent)

  • 보통 코드를 만들 때 가독성을 위해 들여쓰기를 하곤 함
  • C나 Java와 같은 다른 프로그래밍 언어에서는 중괄호({})를 사용하여 영역을 지정하곤 함
  • 하지만 파이썬은 중괄호를 사용하지 않고, 들여쓰기만 사용하여 영역을 지정함

 

 

주석(comment)

  • 프로그램 내부에 프로그램을 설명하기 위해 작성하는 글로, 프로그램 실행에는 전혀 영향을 주지 않음
  • 주석으로 처리하고자 하는 주석 부분 앞에 # 기호를 붙임 (# 이후 해당 줄 전체를 주석 처리 함)  ->행단위 주석
  • 여러 줄을 주석 처리하고 싶을 때는 큰따옴표 """ 또는 작은따옴표 ''' 3개를 연달아서 시작과 끝부분에 사용 -> 블록단위 주석

 

 

행결합

  • 많은 프로그래밍 언어들이 문장(statement)이 끝날 때마다 세미콜론(;)을 적어서 종료를 알리는데,  파이썬은 구문이 끝나면 세미콜론을 붙이지 않고,  줄이 바뀌면 자동으로 문장이 종료되었다고 인식함
  • 만약 1줄의 내용이 길어서 여러 줄로 타이핑을 하고 싶다면 \(역슬래쉬)나 괄호를 이용함

 

print() 함수

  • print() 함수를 통해서 변수, 문자열, 숫자, 수식 결과 등을 쉽게 출력할 수 있음

 

  • 따옴표의 중첩에 따라서 결과가 달라질 수 있음
  • + 연산자를 통해 문자열끼리 더하는 것도 가능함

'Python Category > Python' 카테고리의 다른 글

[Python] 문자형  (0) 2023.08.25
[Python] 파이썬 자료형  (0) 2023.08.24
[Python] 내장 함수  (0) 2022.12.09
[Python] 예외 처리  (2) 2022.12.09
[Python] 파일 입출력  (1) 2022.12.08
import java.util.Scanner;
public class Main {
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
        int number;
        String string;
        System.out.print("숫자 입력 : ");
        number = sc.nextInt();

        System.out.println("문자열 입력 : ");
        string = sc.nextLine();

        System.out.println("숫자 입력 결과 : " + number);
        System.out.println("문자열 입력 결과 : " + string);
	}
}
/*
숫자 입력 : 6546201
문자열 입력 : 
숫자 입력 결과 : 6546201
문자열 입력 결과 : 
*/

의도대로라면 숫자와 문자를 모두 입력을 받아야 하지만, 숫자만 입력받고 프로그램이 종료되었다.

 

이유는 next()는 개행문자(\n)을 무시하고 입력을 받고, 반대로 nextLine()은 개행문자를 포함해서 입력을 받기 때문이다.

따라서 입력 버퍼에는 6546201\n이 들어오게 되고 next()는 개행문자를 무시하기 때문에 6546201만 가져오게 되고, 따라서 버퍼에는 \n만 남게된다.

 

결과적으로 버퍼에 남은 \n을 nextLine()이 가져오게되고 프로그램이 종료된 것이다.

 

next()
버퍼에 입력된 문자나 문자열에서 공백과 개행문자 전까지를 가져온다.
nextLine()
개행문자를 만날 때까지의 문자열 전체를 입력받는다.
버퍼에 입력된 문자열(공백포함)을 개행문자까지 다 가져온다.

 

따라서 이러한 문제를 해결하기 위해서는 버퍼에 잔류하는 내용물을 비워줄 필요가 있다.

즉, 버퍼를 비워줘야 한다.

next()와 nextLine() 사이에 매개 문자열이 없는 nextLine()을 두면 버퍼를 비울 수 있다.

 

import java.util.Scanner;
public class Main {
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
        int number;
        String string;
        System.out.print("숫자 입력 : ");
        number = sc.nextInt();
        
        sc.nextLine();
        
        System.out.println("문자열 입력 : ");
        string = sc.nextLine();

        System.out.println("숫자 입력 결과 : " + number);
        System.out.println("문자열 입력 결과 : " + string);
	}
}
/*
숫자 입력 : 1234
문자열 입력 : 
asd asdasd
숫자 입력 결과 : 1234
문자열 입력 결과 : asd asdasd
*/

 

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


USER 테이블

Oracle 기준

 

Oracle

users.sql
0.00MB

 

MySQL

users.sql
0.00MB

 


 

BOARDS 테이블

Oracle 기준

 

Oracle

boards.sql
0.00MB

 

MySQL

boards.sql
0.00MB

 


 

구현

Oracle

Board.java

import java.util.Date;
public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getBtitle() {
		return btitle;
	}
	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}
	public String getBcontent() {
		return bcontent;
	}
	public void setBcontent(String bcontent) {
		this.bcontent = bcontent;
	}
	public String getBwriter() {
		return bwriter;
	}
	public void setBwriter(String bwriter) {
		this.bwriter = bwriter;
	}
	public Date getBdate() {
		return bdate;
	}
	public void setBdate(Date bdate) {
		this.bdate = bdate;
	}
}

 

BoardExample9.java

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

import ch20.oracle.sec09.exam02.Board;

public class BoardExample9 {
	//Field
	private Scanner scanner = new Scanner(System.in);
	private Connection conn;
	
	//Constructor
	public BoardExample9() {
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);
		} catch(Exception e) {
			e.printStackTrace();
			exit();
		}
	}
	
	//Method	
	public void list() {
		//타이틀 및 컬럼명 출력
		System.out.println();
		System.out.println("[게시물 목록]");
		System.out.println("-----------------------------------------------------------------------");
		System.out.printf("%-6s%-12s%-16s%-40s\n", "no", "writer", "date", "title");
		System.out.println("-----------------------------------------------------------------------");
		
		//boards 테이블에서 게시물 정보를 가져와서 출력하기
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " + 
				"ORDER BY bno DESC";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {		
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.printf("%-6s%-12s%-16s%-40s \n", 
						board.getBno(), 
						board.getBwriter(),
						board.getBdate(),
						board.getBtitle());
			}
			rs.close();
			pstmt.close();
		} catch(SQLException e) {
			e.printStackTrace();
			exit();
		}
		
		//메인 메뉴 출력
		mainMenu();
	}
	
	public void mainMenu() {
		System.out.println();
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("메인 메뉴: 1.Create | 2.Read | 3.Clear | 4.Exit");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		System.out.println();
		
		switch(menuNo) {
			case "1" -> create();
			case "2" -> read();
			case "3" -> clear();
			case "4" -> exit();
		}
	}	
	
	public void create() {
		//입력 받기
		Board board = new Board();
		System.out.println("[새 게시물 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("작성자: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조 메뉴 출력
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 저장
			try {
				String sql = "" +
					"INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate) " +
					"VALUES (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE)";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void read() {
		//입력 받기
		System.out.println("[게시물 읽기]");
		System.out.print("bno: "); 	
		int bno = Integer.parseInt(scanner.nextLine());
		
		//boards 테이블에서 해당 게시물을 가져와 출력
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " +
				"WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, bno);
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.println("#############");
				System.out.println("번호: " + board.getBno());
				System.out.println("제목: " + board.getBtitle());
				System.out.println("내용: " + board.getBcontent());
				System.out.println("작성자: " + board.getBwriter());
				System.out.println("날짜: " + board.getBdate());
				//보조 메뉴 출력
				System.out.println("-------------------------------------------------------------------");
				System.out.println("보조 메뉴: 1.Update | 2.Delete | 3.List");
				System.out.print("메뉴 선택: ");
				String menuNo = scanner.nextLine();
				System.out.println();
				
				if(menuNo.equals("1")) {
					update(board);
				} else if(menuNo.equals("2")) {
					delete(board);
				}
			}
			rs.close();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void update(Board board) {
		//수정 내용 입력 받기
		System.out.println("[수정 내용 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("작성자: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조 메뉴 출력
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에서 게시물 정보 수정
			try {
				String sql = "" +
					"UPDATE boards SET btitle=?, bcontent=?, bwriter=? " +
					"WHERE bno=?";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.setInt(4, board.getBno());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void delete(Board board) {
		//boards 테이블에 게시물 정보 삭제
		try {
			String sql = "DELETE FROM boards WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, board.getBno());
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력		
		list();
	}
	
	public void clear() {
		System.out.println("[게시물 전체 삭제]");
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 전체 삭제
			try {
				String sql = "TRUNCATE TABLE boards";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
			
		//게시물 목록 출력
		list();
	}
	
	public void exit() {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
			}
		}
		System.out.println("** 게시판 종료 **");
		System.exit(0);
	}
	
	public static void main(String[] args) {
		BoardExample9 boardExample = new BoardExample9();
		boardExample.list();
	}
}

 


MySQL

Board.java

import java.util.Date;
public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getBtitle() {
		return btitle;
	}
	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}
	public String getBcontent() {
		return bcontent;
	}
	public void setBcontent(String bcontent) {
		this.bcontent = bcontent;
	}
	public String getBwriter() {
		return bwriter;
	}
	public void setBwriter(String bwriter) {
		this.bwriter = bwriter;
	}
	public Date getBdate() {
		return bdate;
	}
	public void setBdate(Date bdate) {
		this.bdate = bdate;
	}
}

 

BoardExample9.java

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

public class BoardExample9 {
	//Field
	private Scanner scanner = new Scanner(System.in);
	private Connection conn;
	
	//Constructor
	public BoardExample9() {
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
		} catch(Exception e) {
			e.printStackTrace();
			exit();
		}
	}
	
	//Method	
	public void list() {
		//타이틀 및 컬럼명 출력
		System.out.println();
		System.out.println("[게시물 목록]");
		System.out.println("-----------------------------------------------------------------------");
		System.out.printf("%-6s%-12s%-16s%-40s\n", "no", "writer", "date", "title");
		System.out.println("-----------------------------------------------------------------------");
		
		//boads 테이블에서 게시물 정보를 가져와서 출력하기
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " + 
				"ORDER BY bno DESC";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {		
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.printf("%-6s%-12s%-16s%-40s \n", 
						board.getBno(), 
						board.getBwriter(),
						board.getBdate(),
						board.getBtitle());
			}
			rs.close();
			pstmt.close();
		} catch(SQLException e) {
			e.printStackTrace();
			exit();
		}
		
		//메인 메뉴 출력
		mainMenu();
	}
	
	public void mainMenu() {
		System.out.println();
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("메인메뉴: 1.Create | 2.Read | 3.Clear | 4.Exit");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		System.out.println();
		
		switch(menuNo) {
			case "1" -> create();
			case "2" -> read();
			case "3" -> clear();
			case "4" -> exit();
		}
	}	
	
	public void create() {
		//입력 받기
		Board board = new Board();
		System.out.println("[새 게시물 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("글쓴이: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조메뉴 출력
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("보조메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 저장
			try {
				String sql = "" +
					"INSERT INTO boards (btitle, bcontent, bwriter, bdate) " +
					"VALUES (?, ?, ?, now())";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void read() {
		//입력 받기
		System.out.println("[게시물 읽기]");
		System.out.print("bno: "); 	
		int bno = Integer.parseInt(scanner.nextLine());
		
		//boards 테이블에서 해당 게시물을 가져와 출력
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " +
				"WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, bno);
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.println("#############");
				System.out.println("번호: " + board.getBno());
				System.out.println("제목: " + board.getBtitle());
				System.out.println("내용: " + board.getBcontent());
				System.out.println("쓴이: " + board.getBwriter());
				System.out.println("날짜: " + board.getBdate());
				//보조메뉴 출력
				System.out.println("-------------------------------------------------------------------");
				System.out.println("보조메뉴: 1.Update | 2.Delete | 3.List");
				System.out.print("메뉴선택: ");
				String menuNo = scanner.nextLine();
				System.out.println();
				
				if(menuNo.equals("1")) {
					update(board);
				} else if(menuNo.equals("2")) {
					delete(board);
				}
			}
			rs.close();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void update(Board board) {
		//수정 내용 입력 받기
		System.out.println("[수정 내용 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("글쓴이: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조메뉴 출력
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에서 게시물 정보 수정
			try {
				String sql = "" +
					"UPDATE boards SET btitle=?, bcontent=?, bwriter=? " +
					"WHERE bno=?";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.setInt(4, board.getBno());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void delete(Board board) {
		//boards 테이블에 게시물 정보 삭제
		try {
			String sql = "DELETE FROM boards WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, board.getBno());
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력		
		list();
	}
	
	public void clear() {
		System.out.println("[게시물 전체 삭제]");
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 전체 삭제
			try {
				String sql = "TRUNCATE TABLE boards";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
			
		//게시물 목록 출력
		list();
	}
	
	public void exit() {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
			}
		}
		System.out.println("** 게시판 종료 **");
		System.exit(0);
	}
	
	public static void main(String[] args) {
		BoardExample9 boardExample = new BoardExample9();
		boardExample.list();
	}
}

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


트랜잭션

트랜잭션(transaction)은 기능 처리의 최소 단위를 말한다.

하나의 기능은 여러가지 소작업들로 구성된다.

최소 단위라는 것은 이 소작업들을 분리할 수 없으며, 전체를 하나로 본다는 개념이다.

 

트랜잭션은 소작업들이 모두 성공하거나 실패해야 한다.

 

예를 들어 계좌 이체는 출금과 입금으로 구성된 트랜잭션이다.

출금과 입금 작업 중 하나만 성공할 수 없으며, 모두 성공하거나 모두 실패해야 한다.

계좌 이체는 DB 입장에서 보면 두 개의 계좌 금액을 수정하는 작업이다.

출금 계좌에서 금액을 감소시키고, 입금 계좌에서 금액을 증가시킨다.

따라서 아래와 같이 두 개의 UPDATE 문이 필요하다.

두 UPDATE 문은 모두 성공하거나 모두 실패해야 하며, 하나만 성공할 수 없다.

 

DB는 트랜잭션을 처리하기 위해 커밋(commit)과 롤백(rollback)을 제공한다.

커밋은 내부 작업을 모두 성공 처리하고, 롤백은 실행 전으로 돌아간다는 의미에서 모두 실패 처리한다.

 

JDBC에서는 INSERT, UPDATE, DELETE 문을 실행할 때마다 자동 커밋이 일어난다.

 

이 기능은 계좌 이체와 같이 두 가지 UPDATE 문을 실행할 때 문제가 된다.

출금 작업이 성공되면 바로 커밋이 되기 때문에, 입금 작업의 성공 여부와 상관없이 출금 작업만 별도 처리된다.

 

따라서 JDBC에서 트랜잭션을 코드로 제어하려면 자동 커밋 기능을 꺼야 한다.

자동 커밋 설정 여부는 Connection의 setAutoCommit() 메소드로 할 수 있다.

 

아래의 코드는 자동 커밋 기능을 끈다.

conn.setAutoCommit(false);

 

자동 커밋 기능이 꺼지면, 아래와 같은 코드로 커밋과 롤백을 제어할 수 있다.

conn.commit();
conn.rollback();

 

트랜잭션을 처리한 이후에는 원래대로 자동 커밋 기능을 켜둬야 한다.

Connection을 다른 기능 처리를 위해 계속 사용해야 한다면 setAutoCommit(true) 코드로 자동 커밋 기능을 켜둬야 한다.

참고
수동 커밋 모드나 자동 커밋 모드는 한번 설정하면 해당 세션에서는 계속 유지된다. 중간에 변경하는 것은 가능 하다.

특히, 커넥션 풀을 사용할 때 주의해야할 부분이다.

커넥션 풀(Connection Pool)
다수의 클라이언트 요청을 처리하는 서버 프로그램은 대부분 커넥션 풀을 사용한다.
커넥션 풀은 일정량의 Connection을 미리 생성시켜놓고, 서버에서 클라이언트 요청을 처리할 때 Connection을 제공해주고 다시 반환받는 역할을 수행한다.
출처 : 이것이 자바다 유튜브 동영상 강의

커넥션 풀을 사용하면 생성된 Connection을 재사용할 수 있기 때문에 DB 연결 시간을 줄일 수 있고, 전체 Connection 수를 관리할 수도 있다.

 


 

사용 예제

아래의 예제 코드는 하여름의 계좌에서 한겨울의 계좌로 10,000원을 송금하는 예제이다.

물론, DB에 등록되지 않은 계좌로 송금을 하려고 한다던가, 계좌 금액이 부족하면 커밋이 되지 않고 롤백이 된다.

아래의 코드를 실행하고 나면 DB는 아래처럼 된다.

 

Oracle

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

public class TransactionExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//트랜잭션 시작 ----------------------------------------------------
				//자동 커밋 기능 끄기
				conn.setAutoCommit(false);
				
				//출금 작업
				String sql1 = "UPDATE accounts SET balance=balance-? WHERE ano=?";
				PreparedStatement pstmt1 = conn.prepareStatement(sql1);
				pstmt1.setInt(1,  10000);
				pstmt1.setString(2, "111-111-1111");
				int rows1 = pstmt1.executeUpdate();
				if(rows1 == 0) throw new Exception("출금되지 않았음");
				pstmt1.close();
				
				//입금 작업
				String sql2 = "UPDATE accounts SET balance=balance+? WHERE ano=?";
				PreparedStatement pstmt2 = conn.prepareStatement(sql2);
				pstmt2.setInt(1,  10000);
				pstmt2.setString(2, "222-222-2222");
				int rows2 = pstmt2.executeUpdate();
				if(rows2 == 0) throw new Exception("입금되지 않았음");
				pstmt2.close();
			
				//수동 커밋 -> 모두 성공 처리
				conn.commit();
				System.out.println("계좌 이체 성공");	
			//트랜잭션 종료 ----------------------------------------------------
		} catch (Exception e) {
			try { 
				//수동 롤백 -> 모두 실패 처리
				conn.rollback();
			} catch (SQLException e1) {}
			System.out.println("계좌 이체 실패");
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try {
					//원래대로 자동 커밋 기능 켜기
					conn.setAutoCommit(true);
					//연결 끊기
					conn.close();
				} catch (SQLException e) {}
			}
		}
	}
}
/*
계좌 이체 성공
*/

 

 

MySQL

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

public class TransactionExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
			
			//트랜잭션 시작 ----------------------------------------------------
				//자동 커밋 기능 끄기
				conn.setAutoCommit(false);
				
				//출금 작업
				String sql1 = "UPDATE accounts SET balance=balance-? WHERE ano=?";
				PreparedStatement pstmt1 = conn.prepareStatement(sql1);
				pstmt1.setInt(1,  10000);
				pstmt1.setString(2, "111-111-1111");
				int rows1 = pstmt1.executeUpdate();
				if(rows1 == 0) throw new Exception("출금되지 않았음");
				pstmt1.close();
				
				//입금 작업
				String sql2 = "UPDATE accounts SET balance=balance+? WHERE ano=?";
				PreparedStatement pstmt2 = conn.prepareStatement(sql2);
				pstmt2.setInt(1,  10000);
				pstmt2.setString(2, "333-222-2222");
				int rows2 = pstmt2.executeUpdate();
				if(rows2 == 0) throw new Exception("입금되지 않았음");
				pstmt2.close();
			
				//커밋 -> 모두 성공 처리
				conn.commit();
				System.out.println("계좌 이체 성공");	
			//트랜잭션 종료 ----------------------------------------------------
		} catch (Exception e) {
			try { 
				//롤백 -> 모두 실패 처리
				conn.rollback(); 
				//원래대로 자동 커밋 기능 켜기
				conn.setAutoCommit(true);	
			} catch (SQLException e1) {}
			System.out.println("계좌 이체 실패");
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//원래대로 자동 커밋 기능 켜기
					conn.setAutoCommit(true);
					
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


프로시저와 함수

이 글에서는 Oracle DB에 적용되는 프로시저와 함수를 다룬다.

 

클라이언트 프로그램(DB가 서버이고, DB를 이용하는 프로그램이 클라이언트)에서 매개값과 함께 프로시저 또는 함수를 호출하면 DB 내부에서 일련의 SQL문을 실행하고, 실행 결과를 클라이언트 프로그램으로 돌려주는 역할을 한다.

즉, 프로그램 내부에서 SQL문의 작업내용이 처리되지 않고 DB에서 처리가 된다.

클라이언트에서 처리하기 부담스러운 처리를 서버로 돌릴 수 있게 된다.

 

  • 프로시저 : 리턴하는 값이 있지만, 주로 리턴하지 않고 작업 처리를 할 때 사용
    예를 들어, 데이터 삽입, 삭제, 데이터 확인 등
  • 함수 : 작업 처리를 하고 특정 값을 리턴할 때 사용
    예를 들어, 연산 작업을 하고 연산의 값을 리턴

 

JDBC에서 프로시저와 함수를 호출할 때는 CallableStatement를 사용한다.

프로시저와 함수의 매개변수화된 호출문을 작성하고 Connection의 prepareCall() 메소드로부터 CallableStatement객체를 얻을 수 있다.

프로시저와 함수의 매개변수화된 호출문은 조금 차이가 있다.

중괄호로 감싼 call문이라는 점은 동일하지만, 함수는 call 문의 시행 결과를 대입할 좌측 리턴값의 자리(?=)를 명시해야 한다.

 

프로시저명과 함수명의 괄호 안에 작성된 ?는 호출 시 필요한 매개값의 자리이다.

주의할 점은 프로시저도 리턴값과 유사한 OUT 타입의 매개변수를 가질 수 있기 때문에 괄호 안의 ?중 일부는 OUT값(리턴값)일 수 있다는 점이다.

 

PrepareCall() 메소드로 CallableStatement를 얻었다면 리턴 값에 해당하는 ?는 registerOutParameter() 메소드로 지정하고, 그 이외의 ? 는 호출 시 필요한 매개값으로 Setter 메소드를 사용해서 값을 지정해야 한다.

 

함수는 첫 번째 ?가 무조건 리턴값이다.

 

프로시저

String sql = "{call 프로시저명(?, ?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.setString(1, "값");
cstmt.setString(2, "값");
cstmt.registerOutParameter(3, 리턴타입); //세 번째 ?는 OUT값(리턴값)임을 지정

 

함수

String sql = "{? = call 함수명(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.registerOutParameter(1, 리턴타입);//첫 번째 ?는 리턴값임을 지정
cstmt.setString(2, "값");
cstmt.setString(3, "값");
프로시저와 함수의 리턴 값을 받기 위해 registerOutParameter()에 들어가는 두 번째 매개값(리턴 타입)의 종류는 아래의 링크에서 자세히 확인할 수 있다.(공식 API Document)
https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html

 

?에 대한 설정이 끝나면 프로시저 또는 함수를 호출하기 위해 execute() 메소드를 아래와 같이 호출한다.

cstmt.execute();

 

호출 후에는 Getter 메소드로 리턴값을 얻을 수 있다. 리턴 타입이 정수라고 가정하면, 프로시저의 세 번째 ?의 리턴값과 함수의 리턴값은 아래와 같이 얻을 수 있다.

 

더 이상 CallableStatement를 사용하지 않는다면 close() 메소드로 사용했던 메모리를 해제해야 한다.

cstmt.close();

 

 


 

프로시저 호출


create or replace PROCEDURE user_create (
    a_userid        IN  users.userid%TYPE, 
    a_username      IN  users.username%TYPE,
    a_userpassword  IN  users.userpassword%TYPE,
    a_userage       IN  users.userage%TYPE,
    a_useremail     IN  users.useremail%TYPE,
    a_rows          OUT PLS_INTEGER

IS
BEGIN
    INSERT INTO users (userid, username, userpassword, userage, useremail)
    VALUES (a_userid, a_username, a_userpassword,  a_userage, a_useremail);
    a_rows := SQL%ROWCOUNT;
    COMMIT;
END;


DB에 위와 같이 프로시저가 정의되어 있다.

위 프로시저는 SQL문에 의해 실행된 명령(users 테이블 행 추가)의 수를 반환하는 프로시저이다.

즉, 프로그램에서 매개값으로 넘긴 값들을 INSERT문으로 행을 추가하고, 처리된 행의 수를 리턴하는 것이다.

IN 매개변수는 호출 시 필요한 매개값으로 사용되며, OUT 매개변수는 리턴값으로 사용된다.

 

위와 같이 작성된 프로시저를 호출하기 위해 아래와 같이 매개변수화된 호출문을 작성하고 CallableStatement를 얻는다.

String sql = "{call user_create(?, ?, ?, ?, ?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);

위 호출문에서 5번째 ?까지가 매개값이고 6번째 ?가 리턴값이다.

따라서 아래와 같이 ?의 값을 지정하고 리턴 타입을 지정한다.

cstmt.setString(1, "summer");
cstmt.setString(2, "한여름");
cstmt.setString(3, "12345");
cstmt.setInt(4, 26);
cstmt.setString(5, "summer@mycompany.com");
cstmt.registerOutParameter(6, Types.INTEGER);
프로시저와 함수의 리턴 값을 받기 위해 registerOutParameter()에 들어가는 두 번째 매개값(리턴 타입)의 종류는 아래의 링크에서 자세히 확인할 수 있다.(공식 API Document)
https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html

 

이제 user_create 프로시저를 실행하고, 아래와 같이 리턴값을 얻는다.

user_create 프로시저의 리턴값은 사용자 정보가 성공적으로 저장되었을 때 항상 1이 된다.

cstmt.execute();
int rows = cstmt.getInt(6); //6번째 ? 값 얻기

 

사용 예제(Oracle)

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

public class ProcedureCallExample {

	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 호출문 작성과 CallableStatement 얻기
			String sql = "{call user_create(?, ?, ?, ?, ?, ?)}";
			CallableStatement cstmt = conn.prepareCall(sql);
			
			//? 값 지정 및 리턴 타입 지정
			cstmt.setString(1, "summer");
			cstmt.setString(2, "한여름");
			cstmt.setString(3, "12345");
			cstmt.setInt(4, 26);
			cstmt.setString(5, "summer@mycompany.com");
			cstmt.registerOutParameter(6, Types.INTEGER);
			
			
			//함수 실행 및 리턴값 얻기
			cstmt.execute();
			int rows = cstmt.getInt(6);
			System.out.println("저장된 행 수 " + rows);
			
			//CallableStatement 닫기
			cstmt.close();
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
저장된 행 수 1
*/

 


 

함수 호출


create or replace FUNCTION user_login (
    a_userid        users.userid%TYPE, 
    a_userpassword  users.userpassword%TYPE
) RETURN PLS_INTEGER
IS
    v_userpassword users.userpassword%TYPE;
    v_result PLS_INTEGER;
BEGIN
    SELECT userpassword INTO v_userpassword
    FROM users
    WHERE userid = a_userid;

    IF v_userpassword = a_userpassword THEN
        RETURN 0;
    ELSE
        RETURN 1;
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN 
        RETURN 2;
END;

DB에 함수는 위와 같이 선언되어 있다.

 

user_login()은 2개의 매개변수와 PLS_INTEGER 리턴 타입으로 구성되어 있다. 2개의 매개변수는 호출 시 값을 제공하고, 호출 후에는 정수 값을 리턴한다.

 

위 함수는 받은 매개값(ID와 PW) 을 DB에서 검색을 하고, ID와 PW가 일치하면 0을 리턴하고, 일치하지 않으면 1을 리턴하고, 해당 ID가 없으면 2를 리턴한다.

 

user_login() 함수를 호출하기 위해 아래와 같이 매개변수화된 호출문을 작성하고 CallableStatement를 얻는다.

String sql = "{? = call user_login(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);

첫 번째 ?가 리턴값이고, 괄호 안에 있는 ?들이 매개값이다.

그래서 아래와 같이 ?의 값을 지정하고 리턴 타입을 지정한다.

프로시저와 함수의 리턴 값을 받기 위해 registerOutParameter()에 들어가는 두 번째 매개값(리턴 타입)의 종류는 아래의 링크에서 자세히 확인할 수 있다.(공식 API Document)
https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html
cstmt.registerOutParameter(1, Types.INTEGER);
cstmt.setString(2, "winter");
cstmt.setString(3, "12345");

 

user_login() 함수는 userid와 userpassword가 일치하면 0을, userpassword가 틀리면 1을, userid가 존재하지 않으면 2를 리턴한다.

cstmt.execute();
int result = cstmt.getInt(1);

 

사용 예제(Oracle)

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

public class FunctionCallExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 호출문 작성과 CallableStatement 얻기
			String sql = "{? = call user_login(?, ?)}";
			CallableStatement cstmt = conn.prepareCall(sql);
			
			//? 값 지정 및 리턴 타입 지정
			cstmt.registerOutParameter(1, Types.INTEGER);
			cstmt.setString(2, "winter");
			cstmt.setString(3, "12345");
			
			//함수 실행 및 리턴값 얻기
			cstmt.execute();
			int result = cstmt.getInt(1);
			
			//CallableStatement 닫기
			cstmt.close();
			
			//로그인 결과(Switch Expressions 이용)
			String message = switch(result) {
				case 0 -> "로그인 성공";
				case 1 -> "비밀번호가 틀림";
				default -> "아이디가 존재하지 않음";
			};
			System.out.println(message);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
로그인 성공
*/

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


데이터 베이스 구성

Account Table

 

Boards

 

Users

 

 


 

데이터 저장

데이터 쓰기(INSERT문)

User 테이블

users 테이블에 새로운 사용자 정보를 저장하는 INSERT 문은 아래와 같다.


INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES ('winter', '한겨울', '12345', '25', 'winter@mycompany.com')

 

값을 ?로 대체한 매개변수화된 INSERT 문으로 변경하면 아래와 같다.


INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES (?, ?, ?, ?, ?)

 

그리고 INSERT 문을 String 타입 변수 sql에 문자열로 대입한다.

String sql = new StringBuilder()
        .append("INSERT INTO users (userid, username, userpassword, userage, useremail) ")//끝에 띄어쓰기를 꼭 해줘야한다.
        .append("VALUES (?, ?, ?, ?, ?)")
        .toString();
		
//또는

String sql = "INSERT INTO users(userid, username, userpassword, userage, useremail) " + "VALUES(?, ?, ?, ?, ?)";

 

매개변수화된 SQL 문을 실행하려면 PreparedStatement가 필요하다.

아래와 같이 Connection의 prepareStatement() 메소드로부터 PreparedStatement를 얻는다.

PreparedStatement pstmt = conn.prepareStatement(sql);

 

그리고 ?에 들어갈 값을 지정해주는데, ?는 순서에 따라 1번부터 번호가 부여된다.

값의 타입에 따라 setter 메소드를 선택한 후 첫 번째 매개값에는 ?순번, 두 번째 매개값에는 값을 지정한다.

//userid, username, userpassword, userage, useremail
//VALUES (?, ?, ?, ?, ?)
pstmt.setString(1, "winter2");
pstmt.setString(2, "한겨울");
pstmt.setString(3, "12345");
pstmt.setInt(4, 25);
pstmt.setString(5, "winter@mycompany.com");

 

값을 지정한 후 executeUpdate() 메소드를 호출하면 SQL 문이 실행되면서 users 테이블에 1개의 행이 저장된다.

executeUpdate() 메소드가 리턴하는 값은 실행된 명령문 수인데, 정상적으로 실행되었을 경우 1을 리턴한다.

int rows = pstmt.executeUpdate();

 

PreparedStatement를 더 이상 사용하지 않을 경우에는 close() 메소드를 호출해서 메모리를 해제한다.

pstmt.close();

 

사용 예제(Oracle)

더보기
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class UserInsertExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 SQL 문 작성
			String sql = "" +
				"INSERT INTO users(userid, username, userpassword, userage, useremail)" + "VALUES(?, ?, ?, ?, ?)";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter2");
			pstmt.setString(2, "한겨울");
			pstmt.setString(3, "12345");
			pstmt.setInt(4, 25);
			pstmt.setString(5, "winter@mycompany.com");
			
			//SQL 문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("저장된 행 수: " + rows);
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
저장된 행 수: 1
*/

정상적으로 DB에 추가된 것을 볼 수 있다.

 

사용 예제(MySQL)

더보기
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class UserInsertExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);	
			
			//매개변수화된 SQL문 작성
			String sql = "" +
				"INSERT INTO users (userid, username, userpassword, userage, useremail) " +
				"VALUES (?, ?, ?, ?, ?)";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			pstmt.setString(2, "한겨울");
			pstmt.setString(3, "12345");
			pstmt.setInt(4, 25);
			pstmt.setString(5, "winter@mycompany.com");
			
			//SQL문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("저장된 행 수: " + rows);
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 


 

boards 테이블

새로운 게시물 정보를 저장하는 INSERT 문은 아래와 같다.

SEQ_BNO.NEXTVAL은 SEQ_BNO 시퀀스에서 가져올 번호이고, SYSDATE는 현재 시간이다.

SEQ_BNO 시퀀스
SEQ_BNO라는 이름을 가진 시퀀스이다.
중간에 게시글이 삭제되면 누락된 인덱스 없이 자동으로 순서를 잡아준다.

INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata)
VALUES (SEQ_BNO.NEXTVAL, '눈 오는 날', '함박눈이 내려요', 'winter', SYSDATE, 'snow.jpg', 'binaryData')

 

SEQ_BNO.NEXTVAL와 SYSDATE를 제외하고 나머지는 ?로 대체한 매개변수화된 INSERT 문으로 만들고 String 타입 변수 sql에 저장한다.

String sql = "" +
    "INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata) " +
    "VALUES (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE, ?, ?)";

 

매개변수화된 INSERT 문을 실행하기 위해 아래와 같이 prepareStatement() 메소드로부터 PreparedStatement를 얻는데, 두 번째 매개값은 INSERT 문이 실행된 후 가져올 컬럼 값이다.

PreparedStatement pstmt = conn.prepareStatement(sql, new String[] {"bno"});

위 코드는 bno 컬럼 값을 가져온다.

SQL문이 실행되기 전까지는 SEQ_BNO.NEXTVAL로 얻은 번호를 모르기 때문에 SQL 문이 실행된 후에 bno 컬럼에 실제로 저장된 값을 얻는 것이다.

 

bfiledata 컬럼은 바이너리 타입(blob)이므로 ?에 값을 지정하려면 setBinaryStream(), setBlob(), setByte() 메소드 중 하나를 이용해야 한다.

pstmt.setString(1, "눈오는 날");
pstmt.setString(2, "함박눈이 내려요.");
pstmt.setString(3, "winter");
pstmt.setString(4, "snow.jpg");
pstmt.setBlob(5, new FileInputStream("src/ch20/oracle/sec06/snow.jpg"));

 

INSERT 문을 실행하고 저장된 bno 값을 얻는 방법은 아래와 같다.

게시물 정보가 저장되었을 경우(row가 1일 경우) getGeneratedKeys() 메소드로 resultSet을 얻고, getInt() 메소드로 bno를 얻는다.

//{"bno"}
int rows = pstmt.executeUpdate();
if(rows == 1) {
    ResultSet rs = pstmt.getGeneratedKeys();
    if(rs.next()) {
        int bno = rs.getInt(1);
    }
    rs.close();
}

만약 btitle을 얻고싶다면 아래처럼 하면 된다.

PreparedStatement pstmt = conn.prepareStatement(sql, new String[] {"bno", "btitle"});
int rows = pstmt.executeUpdate();
if(rows == 1) {
    ResultSet rs = pstmt.getGeneratedKeys();
    if(rs.next()) {
        string btitle = rs.getInt(2);
    }
    rs.close();
}

 

실행 예제(oracle)

더보기
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BoardWithFileInsertExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 SQL 문 작성
			String sql = "" +
				"INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata) " +
				"VALUES (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE, ?, ?)";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql, new String[] {"bno"});
			pstmt.setString(1, "눈오는 날");
			pstmt.setString(2, "함박눈이 내려요.");
			pstmt.setString(3, "winter");
			pstmt.setString(4, "snow.jpg");
			pstmt.setBlob(5, new FileInputStream("src/ch20/oracle/sec06/snow.jpg"));
			
			//SQL 문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("저장된 행 수: " + rows);
			
			//bno 값 얻기
			if(rows == 1) {
				ResultSet rs = pstmt.getGeneratedKeys();
				if(rs.next()) {
					int bno = rs.getInt(1);
					System.out.println("저장된 bno: " + bno);
				}
				rs.close();
			}
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
저장된 행 수: 1
저장된 bno: 1
*/

정상적으로 DB에 저장된 것을 볼 수 있다.

 

실행 예제(MySQL)

더보기
import java.io.FileInputStream;
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 BoardInsertExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
			
			//매개변수화된 SQL문 작성
			String sql = "" +
				"INSERT INTO boards (btitle, bcontent, bwriter, bdate, bfilename, bfiledata) " +
				"VALUES (?, ?, ?, now(), ?, ?)";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(
					sql, Statement.RETURN_GENERATED_KEYS);
			pstmt.setString(1, "눈오는 날");
			pstmt.setString(2, "함박눈이 내려요.");
			pstmt.setString(3, "winter");
			pstmt.setString(4, "snow.jpg");
			pstmt.setBlob(5, new FileInputStream("src/ch20/mysql/sec06/snow.jpg"));
			
			//SQL문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("저장된 행 수: " + rows);
			
			//bno 값 얻기
			if(rows == 1) {
				ResultSet rs = pstmt.getGeneratedKeys();
				if(rs.next()) {
					int bno = rs.getInt(1);
					System.out.println("저장된 bno: " + bno);
				}
				rs.close();
			}
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 

 


 

데이터 수정

boards 테이블에 저장된 게시물 중에서 bno가 1인 게시물의 btitle, bcontent, bfilename, bfiledata를 변경하는 SQL문은 아래와 같다.


UPDATE boards SET
btitle = '눈사람',
bcontent = '눈으로 만든 사람',
bfilename = 'snowman.jpg',
bfiledata = binaryData
WHERE bno = 1

 

값을 ?로 대체한 매개변수화된 UPDATE 문으로 변경한다.


UPDATE boards SET
btitle = ?,
bcontent = ?,
bfilename = ?,
bfiledata = ?
WHERE bno = ?

 

String 타입 변수 sql에 매개변수화된 UPDATE 문을 저장한다.

String sql = new StringBuilder()
    .append("UPDATE boards SET ")
    .append("btitle=?, ")
    .append("bcontent=?, ")
    .append("bfilename=?, ")
    .append("bfiledata=? ")
    .append("WHERE bno=?")
    .toString();

 

매개변수화된 UPDATE 문을 실행하기 위해 아래와 같이 prepareStatement() 메소드로부터 PreparedStatement를 얻고, ?에 해당하는 값을 지정한다.

PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "눈사람");
pstmt.setString(2, "눈으로 만든 사람");
pstmt.setString(3, "snowman.jpg");
pstmt.setBlob(4, new FileInputStream("src/ch20/mysql/sec07/snowman.jpg"));
pstmt.setInt(5, 3);

 

값을 모두 지정하였다면 UPDATE 문을 실행하기 위해 executeUpdate() 메소드를 호출한다.

 

성공적으로 실행되면 수정된 행의 수가 리턴된다.

int rows = pstmt.executeUpdate();

 

 

실행 예제(Oracle)

더보기
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BoardUpdateExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 SQL 문 작성
			String sql = new StringBuilder()
					.append("UPDATE boards SET ")
					.append("btitle=?, ")
					.append("bcontent=?, ")
					.append("bfilename=?, ")
					.append("bfiledata=? ")
					.append("WHERE bno=?")
					.toString();
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "눈사람");
			pstmt.setString(2, "눈으로 만든 사람");
			pstmt.setString(3, "snowman.jpg");
			pstmt.setBlob(4, new FileInputStream("src/ch20/oracle/sec07/snowman.jpg"));
			pstmt.setInt(5, 3);  //boards 테이블에 있는 게시물 번호(bno) 지정
			
			//SQL 문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("수정된 행 수: " + rows);
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 

실행 예제(MySQL)

더보기
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BoardUpdateExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
			
			//매개변수화된 SQL문 작성
			String sql = new StringBuilder()
					.append("UPDATE boards SET ")
					.append("btitle=?, ")
					.append("bcontent=?, ")
					.append("bfilename=?, ")
					.append("bfiledata=? ")
					.append("WHERE bno=?")
					.toString();
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "눈사람");
			pstmt.setString(2, "눈으로 만든 사람");
			pstmt.setString(3, "snowman.jpg");
			pstmt.setBlob(4, new FileInputStream("src/ch20/mysql/sec07/snowman.jpg"));
			pstmt.setInt(5, 3);  //boards 테이블에 있는 게시물 번호(bno) 지정
			
			//SQL문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("수정된 행 수: " + rows);
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 

 


 

데이터 삭제

boards 테이블에서 bwriter가 winter인 모든 게시물을 삭제하는 DELETE 문은 아래와 같다.


DELETE FROM boards WHERE bwriter = 'winter'

 

조건절의 값을 ?로 대체한 매개변수화된 DELETE 문으로 변경한다.

String sql = "DELETE FROM boards WHERE bwriter=?";

 

매개변수화된 DLELTE 문을 실행하기 위해 아래와 같이 prepareStatement() 메소드로부터 PreparedStatement를 얻고 ?에 값을 지정한 후, executeUpdate로 SQL문을 실행한다.

리턴 값은 삭제된 행의 수이다.

String sql = "DELETE FROM boards WHERE bwriter=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");
int rows = pstmt.executeUpdate();

 

 

실행 예제(Oracle)

더보기
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BoardDeleteExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 SQL 문 작성
			String sql = "DELETE FROM boards WHERE bwriter=?";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			//SQL 문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("삭제된 행 수: " + rows);
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 

실행 예제(MySQL)

더보기
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BoardDeleteExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
			
			//매개변수화된 SQL문 작성
			String sql = "DELETE FROM boards WHERE bwriter=?";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			//SQL문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("삭제된 행 수: " + rows);
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 

 


 

데이터 읽기

DB에서 데이터를 가져오는 메소드

데이터를 가져오는 SELECT 문일 경우에는 executeQuery() 메소드를 호출해야 한다.

executeQuery() 메소드는 가져온 데이터를 ResultSet에 저장하고 리턴한다.

ResultSet rs = pstmt.executeQuery();

 


 

ResultSet 구조

ResultSet은 SELECT 문에 기술된 컬럼으로 구성된 행(row)의 집합이다.

예를 들어 아래의 SELECT문은 userid, username, userage 컬럼으로 구성된 ResultSet을 리턴한다.


SELECT userid, username, userage FROM user

위의 SELECT 문이 가져온 데이터 행이 4개라면 ResultSet의 내부 구조는 아래와 같다.

출처 : 이것이 자바다 유튜브 동영상 강의

ResultSet의 특정인 커서가 있는 행의 데이터만 읽을 수 있다는 것이다.

커서는 행을 가리키는 포인터를 말한다.

ResultSet에는 실제 가져온 데이터 행의 앞과 뒤에 beforeFirst 행과 afterLast 행이 붙는데, 최초 커서는 beforeFirst를 가리킨다.

따라서 첫 번째 데이터 행인 first 행을 읽으려면 커서를 이동시켜야 한다.

커서를 한 칸 아래로 이동시킬 땐, next() 메소드를 사용한다.

booleat result = rs.next();

next() 메소드는 이동한 행에 데이터가 있으면 true를, 없으면 false를 리턴한다.

즉, last 행까지는 true를 리턴하고, afterLast 행으로 이동하면 false를 리턴한다.

 

1개의 데이터 행만 가져올 경우 if문 조건식에 next()으로 처리하고, 모든 데이터를 가져올 때는 while문 조건식에 next()를 이용한다.

 

커서를 자유자재로 이동하기
PreparedStatement pstmt = conn.prepareStatement(sql);​
위 코드의 매개값에는 두 번째 매개값과 세 번째 매개값에 자동으로 노란색으로 칠한 매개값이 들어가 있다.
두 번째 매개값 : TYPE_FORWORD_ONLY
세 번째 매개값 : CONCUR_READ_ONLY
즉, 커서가 뒤로 이동할 수 없고 오직 앞으로만 이동 가능하며, 읽기 전용이라는 뜻이다.

두 번째 매개값과 세 번째 매개값에 초록색으로 칠한 매개값을 넣어주면 ResultSet 메소드를 통해 커서를 자유자재로 이동할 수 있고, 값 수정도 가능하다.
PreparedStatement pstmt = conn.prepareStatement(sql, 
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

 

SELECT 문에 따라 ResultSet에는 많은 데이터 행이 저장될 수 있기 때문에 ResultSet을 더 이상 사용하지 않는다면 close() 메소드를 호출해서 사용한 메모리를 해제하는 것이 좋다.

rs.close();

 


 

데이터 행 읽기

커서가 있는 데이터 행에서 각 컬럼의 값은 Getter 메소드로 읽을 수 있다.

컬럼의 데이터 타입에 따라 getXxx() 메소드가 사용되며, 매개값으로 컬럼의 이름 또는 컬럼 순번을 줄 수 있다.

ResultSet에서 컬럼 순번은 1부터 시작하기 때문에 userid = 1, username = 2, userage = 3이 된다.

만약 SELECT 문에 연산식이나 함수 호출이 포함되어 있다면 컬럼 이름 대신에 컬럼 순번으로 읽어야 한다.
예를 들어 아래와 같은 SELECT 문에서 userage -1 연산식이 사용되면 컬럼 순번으로만 읽을 수 있다.
userage -1은 컬럼명이 아니기 때문이다.
(userage -1) as userage와 같이 별명이 있다면 별명이 컬럼 이름이 된다.

 


 

데이터 추출

users 테이블

어떤 조건에 만족하는 데이터만 가져오려면 WHERE 에 조건을 명시한다.

예를 들어, userid가 winter인 사용자 정보를 가져오는 SELECT 문은 아래와 같다.


SELECT userid, username, userpassword, userage, useremail
FROM users
WHERE userid = 'winter';

 

조건절의 값을 ?로 대체한 매개변수화된 SQL 문을 String 타입 변수 sql에 대입한다.

String sql = "" +
    "SELECT userid, username, userpassword, userage, useremail " +
    "FROM users " +
    "WHERE userid=?";

 

매개변수화된 SELECT 문을 실행하기 위해 아래와 같이 prepareStatement() 메소드로부터 PreparedStatement를 얻고, ?에 값을 지정한다.

PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");

 

아래의 코드에서 User클래스는 DTO이다.

관련 데이터를 묶어서 하나의 객체로 즉, DTO로 만들어서 수신과 전송을 한다는 뜻이다.

 

사용 예제(Oracle)

더보기

User.java

public class User {
	private String userId;
	private String userName;
	private String userPassword;
	private int userAge;
	private String userEmail;
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getUserPassword() {
		return userPassword;
	}
	public void setUserPassword(String userPassword) {
		this.userPassword = userPassword;
	}
	public int getUserAge() {
		return userAge;
	}
	public void setUserAge(int userAge) {
		this.userAge = userAge;
	}
	public String getUserEmail() {
		return userEmail;
	}
	public void setUserEmail(String userEmail) {
		this.userEmail = userEmail;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userPassword=" + userPassword + ", userAge="
				+ userAge + ", userEmail=" + userEmail + "]";
	}
}

 

UserSelectExample.java

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

public class UserSelectExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 SQL 문 작성
			String sql = "" +
				"SELECT userid, username, userpassword, userage, useremail " +
				"FROM users " +
				"WHERE userid=?";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			//SQL 문 실행 후, ResultSet을 통해 데이터 읽기
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {						//1개의 데이터 행을 가져왔을 경우
				User user = new User();				
				user.setUserId(rs.getString("userid"));
				user.setUserName(rs.getString("username"));
				user.setUserPassword(rs.getString("userpassword"));
				user.setUserAge(rs.getInt(4));       //컬럼 순번을 이용
				user.setUserEmail(rs.getString(5));  //컬럼 순번을 이용				
				System.out.println(user);
			} else {                           //데이터 행을 가져오지 않았을 경우
				System.out.println("사용자 아이디가 존재하지 않음");
			}
			rs.close();
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
User [userId=winter, userName=한겨울, userPassword=12345, userAge=25, userEmail=winter@mycompany.com]
*/

 

사용 예제(MySQL)

더보기

 User.java

public class User {
	private String userId;
	private String userName;
	private String userPassword;
	private int userAge;
	private String userEmail;
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getUserPassword() {
		return userPassword;
	}
	public void setUserPassword(String userPassword) {
		this.userPassword = userPassword;
	}
	public int getUserAge() {
		return userAge;
	}
	public void setUserAge(int userAge) {
		this.userAge = userAge;
	}
	public String getUserEmail() {
		return userEmail;
	}
	public void setUserEmail(String userEmail) {
		this.userEmail = userEmail;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userPassword=" + userPassword + ", userAge="
				+ userAge + ", userEmail=" + userEmail + "]";
	}
}

 

UserSelectExample.java

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

public class UserSelectExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);	
			
			//매개변수화된 SQL문 작성
			String sql = "" +
				"SELECT userid, username, userpassword, userage, useremail " +
				"FROM users " +
				"WHERE userid=?";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			//SQL문 실행 후, ResultSet을 통해 데이터 읽기
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {						//1개의 데이터 행을 가져왔을 경우
				User user = new User();				
				user.setUserId(rs.getString("userid"));
				user.setUserName(rs.getString("username"));
				user.setUserPassword(rs.getString("userpassword"));
				user.setUserAge(rs.getInt(4));       //컬럼 순번을 이용
				user.setUserEmail(rs.getString(5));  //컬럼 순번을 이용				
				System.out.println(user);
			} else {                           //데이터 행을 가져오지 않았을 경우
				System.out.println("사용자 아이디가 존재하지 않음");
			}
			rs.close();
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

 


 

boards 테이블

현재 boards 테이블의 상태는 아래와 같다.

 

bwriter가 winter인 게시물 정보를 가져오는 SELECT 문은 아래와 같다.


SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata
FROM boards
WHERE bwriter = 'winter';

 

조건절의 값을 ?로 대체한 매개변수화된 SELECT 문을 String 타입 변수 sql에 대입한다.

String sql = "" +
    "SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " +
    "FROM boards " +
    "WHERE bwriter=?";

 

매개변수화된 SELECT 문을 실행하기 위해 아래와 같이 prepareStatement() 메소드로부터 PreparedStatement를 얻고, ?에 값을 지정한다.

PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");

 

bfiledata는 Blob 객체이므로 콘솔에 출력하면 의미 없는 타입 정보만 출력된다.

Blob 객체에 저장된 바이너리 데이터를 얻기 위해서는 아래와 같이 입력 스트림 또는 바이트 배열을 얻어내야 한다.

  • 입력스트림을 사용할 때 : 파일을 저장할 때 또는 전송할 때
  • 바이트 배열을 사용할 때 :  UI프로그램 등에서 화면상에서 그림을 그려야할 때

 

아래의 코드는 Blob 객체에서 InputStream을 얻고, 읽은 바이트를 파일로 저장하는 방법을 보여준다.

InputStream is = blob.getBinaryStream();
OutputStream os = new FileOutputStream("C:/Temp/" + board.getBfilename());
is.transferTo(os);
os.flush();
os.close();
is.close();

 

transferTo()메소드
기존에는 입력스트림을 출력스트림으로 전달하려면 아래와 같은 코드를 작성해야 했다.
byte[] data = new byte[1024];
while(true)
{
    int num = is.read(data);
    if (num == -1) break;
    os.write(data, 0, num);
}​


하지만
transfer()메소드를 사용하면 아래와 같이 짧게 코드를 작성할 수 있다.

is.transferTo(os)

 

아래의 코드에서 Board 클래스는 DTO이다.

관련 데이터를 묶어서 하나의 객체로 즉, DTO로 만들어서 수신과 전송을 한다는 뜻이다.

 

사용 예제(Oracle)

더보기

Board.java

import java.sql.Blob;
import java.util.Date;
import lombok.Data;

public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	private String bfilename;
	private Blob bfiledata;
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getBtitle() {
		return btitle;
	}
	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}
	public String getBcontent() {
		return bcontent;
	}
	public void setBcontent(String bcontent) {
		this.bcontent = bcontent;
	}
	public String getBwriter() {
		return bwriter;
	}
	public void setBwriter(String bwriter) {
		this.bwriter = bwriter;
	}
	public Date getBdate() {
		return bdate;
	}
	public void setBdate(Date bdate) {
		this.bdate = bdate;
	}
	public String getBfilename() {
		return bfilename;
	}
	public void setBfilename(String bfilename) {
		this.bfilename = bfilename;
	}
	public Blob getBfiledata() {
		return bfiledata;
	}
	public void setBfiledata(Blob bfiledata) {
		this.bfiledata = bfiledata;
	}
	@Override
	public String toString() {
		return "Board [bno=" + bno + ", btitle=" + btitle + ", bcontent=" + bcontent + ", bwriter=" + bwriter
				+ ", bdate=" + bdate + ", bfilename=" + bfilename + ", bfiledata=" + bfiledata + "]";
	}
}

 

BoardSelectExample.java

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BoardSelectExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 SQL 문 작성
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " +
				"FROM boards " +
				"WHERE bwriter=?";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			//SQL 문 실행 후, ResultSet을 통해 데이터 읽기
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {		
				//데이터 행을 읽고 Board 객체 생성
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				board.setBfilename(rs.getString("bfilename"));
				board.setBfiledata(rs.getBlob("bfiledata"));
				
				//콘솔에 출력
				System.out.println(board);
				
				//파일로 저장
				Blob blob = board.getBfiledata();
				if(blob != null) {
					InputStream is = blob.getBinaryStream();
					OutputStream os = new FileOutputStream("C:/Temp/" + board.getBfilename());
					is.transferTo(os);
					os.flush();
					os.close();
					is.close();
				}
			}
			rs.close();
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
Board [bno=2, btitle=눈오는 날, bcontent=함박눈이 내려요., bwriter=winter, bdate=2023-08-06, bfilename=snow.jpg, bfiledata=oracle.sql.BLOB@305ffe9e]
Board [bno=1, btitle=눈오는 날, bcontent=함박눈이 내려요., bwriter=winter, bdate=2023-08-05, bfilename=snow.jpg, bfiledata=oracle.sql.BLOB@35841320]
*/

 

사용 예제(MySQL)

더보기

Board.java

import java.sql.Blob;
import java.util.Date;
import lombok.Data;

public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	private String bfilename;
	private Blob bfiledata;
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getBtitle() {
		return btitle;
	}
	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}
	public String getBcontent() {
		return bcontent;
	}
	public void setBcontent(String bcontent) {
		this.bcontent = bcontent;
	}
	public String getBwriter() {
		return bwriter;
	}
	public void setBwriter(String bwriter) {
		this.bwriter = bwriter;
	}
	public Date getBdate() {
		return bdate;
	}
	public void setBdate(Date bdate) {
		this.bdate = bdate;
	}
	public String getBfilename() {
		return bfilename;
	}
	public void setBfilename(String bfilename) {
		this.bfilename = bfilename;
	}
	public Blob getBfiledata() {
		return bfiledata;
	}
	public void setBfiledata(Blob bfiledata) {
		this.bfiledata = bfiledata;
	}
	@Override
	public String toString() {
		return "Board [bno=" + bno + ", btitle=" + btitle + ", bcontent=" + bcontent + ", bwriter=" + bwriter
				+ ", bdate=" + bdate + ", bfilename=" + bfilename + ", bfiledata=" + bfiledata + "]";
	}
}

 

.BoardSelectExample.java

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BoardSelectExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
			
			//매개변수화된 SQL문 작성
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " +
				"FROM boards " +
				"WHERE bwriter=?";
			
			//PreparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			//SQL문 실행 후, ResultSet을 통해 데이터 읽기
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {		
				//데이터 행을 읽고 Board 객체 생성
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				board.setBfilename(rs.getString("bfilename"));
				board.setBfiledata(rs.getBlob("bfiledata"));
				
				//콘솔에 출력
				System.out.println(board);
				
				//파일로 저장
				Blob blob = board.getBfiledata();
				if(blob != null) {
					InputStream is = blob.getBinaryStream();
					OutputStream os = new FileOutputStream("C:/Temp/" + board.getBfilename());
					is.transferTo(os);
					os.flush();
					os.close();
					is.close();
				}
			}
			rs.close();
			
			//PreparedStatement 닫기
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

'Java Category > Java' 카테고리의 다른 글

[Java] DB 트랜잭션 처리  (0) 2023.08.20
[Java] DB 프로시저와 함수 호출  (0) 2023.08.19
[Java] JDBC 개요 및 DB 연결하기  (0) 2023.08.17
[Java] TCP 채팅 프로그램  (0) 2023.08.16
[Java] JSON 데이터 형식  (0) 2023.08.15

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


JDBC

자바는 데이터베이스와 연결해서 데이터 입출력 작업을 할 수 있도록 JDBC(Java Database Connectivity) 라이브러리 (java.sql 패키지)를 제공한다.

JDBC는 데이터베이스 관리시스템(DBMS)의 종류와 상관없이 동일하게 사용할 수 있는 클래스와 인터페이스로 구성되어 있다.

JDBC는 인터페이스다. 즉 구현 객체가 있어야 사용할 수 있다.

구현 객체는 JDBC Driver인데, 해당 구현 객체는 DBMS 제조사가 제공한다.

JDBC Driver는 DBMS 마다 별도로 다운로드받아 사용해야 한다.

 

출처 : 이것이 자바다 유튜브 동영상 강의

위 그림은 상속 관계를 나타낸 그림이 아니다.

 

  • DriverManager : JDBC Driver를 관리하며 DB와 연결해서 Connection 구현 객체를 생성
  • Connection : Statement, PreparedStatement, CallableStatement 구현 객체를 생성하며, 트랜잭션 처리 및 DB 연결을 끊을 때 사용
  • Statement : SQL의 DDL(Data Definition Language)과 DML(Data Manipulation Language)을 실행할 때 사용한다. 주로 변경되지 않는 정적 SQL 문을 실행할 때 사용한다.
  • PreparedStatement : Statement와 동일하게 DDL, DML문을 처리할 때 사용한다.
    차이점은 매개변수화된 SQL문을 사용할 수 있기 때문에 편리성과 보안성이 좋다.
  • CallableStatement : DB에 저장되어 있는 프로시저와 함수를 호출할 때 사용한다.

2023.11.19 - [데이터베이스/데이터베이스 개론] - SQL분류(DML, DDL, DCL, TCL)

 

SQL분류(DML, DDL, DCL, TCL)

SQL(Structured Query Language) DML(Data Manipulation Language) 데이터 조작 언어 데이터를 조작(수정, 삭제, 삽입, 선택)하는데 사용되는 언어 DML 구문이 사용되는 대상은 테이블의 행 DML 구문을 사용하기 위해

rebugs.tistory.com


 

DB 연결

DBMS는 서버이고 이 데이터베이스를 이용하는 프로그램은 클라이언트가 된다.

클라이언트 프로그램에서 DB와 연결하려면 해당 DBMS의 JDBC Driver가 필요하다.

또한 연결에 필요한 아래의 네가지 정보가 필요하다.

출처 : 이것이 자바다 유튜브 동영상 강의

  • IP주소 : 해당 데이터 베이스가 있는 컴퓨터에 들어가기 위한 주소
  • 포트 번호 : 컴퓨터 내에서도 여러 포트중에 어디로 가야할지 정해주는 번호
  • DB 계정 및 비밀번호 : 보안을 위한 과정
  • DB 이름 : DBMS 안에도 여러 DB가 있기 때문에 어떤 DB에 연결할지

 

JDBC Driver - Build Path

DBMS사에서 제공하는 JDBC Driver를 다운로드를 받고, 자바 IDE에 해당 JAR 파일을 Build Path해준다.

Oracle DBMS를 사용했기때문에 ojdbc8을 추가했다.


JDBC Driver를 메모리로 로딩

로딩이란, 바이트 코드 파일을 메소드 영역에 올리는 것을 말한다.

Class.forName() 메소드는 문자열로 주어진 JDBC Driver 클래스를 Bulid Path에서 찾고, 메모리로 로딩한다.

Class.forName("oracle.jdbc.OracleDriver");//ojdbc8 안에 oracle.jdbc.OracleDriver를 로딩
Class.forName() 메소드는 매개값에 주어진 클래스 파일을 리턴하는데 이를 받아줄 변수가 없으면 해당 클래스 파일을 로딩만 해준다.

위 과정에서 JDBC Driver 클래스의 static 블록이 실행되면서 Driver Manager에 JDBC Driver 객체를 등록하게 된다.

class OracleDriver{
	static { //메모리에 로딩
		Driver dirver = new OracleDriver();
		DriverManager.registerDriver(driver);
	}
}

 

만약 Bulid Path에서 JDBC Driver 클래스를 찾지 못하면 ClassNotFoundException이 발생하므로 예외 처리를 해야한다.

 

Driver Manager에 JDBC Driver가 등록되면 getConnection() 메소드로 DB와 연결할 수 있다.

Connection conn = DriverManager.getConnection("연결 문자열", "사용자", "비밀번호");

첫 번째 매개값은 연결 문자열인데, DBMS마다 다른 형식을 가지고 있다.

아래는 Oracle의 연결 문자열을 보여준다.

jdbc:oracle:thin은 TCP용 JDBC Driver를 사용한다는 뜻이다.

 

이렇게 위에서 언급한 DB에 연결하기 위한 4가지 정보를 제공함으로써 DB에 연결할 수 있게된다.

 

사용 예제

Oracle SQL

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

public class ConnectionExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");

			//연결하기
			conn = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521/orcl",
					"java",
					"oracle"
					);
			
			System.out.println("연결 성공");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try {
					//연결 끊기
					conn.close();
					System.out.println("연결 끊기");
				} catch (SQLException e) {}
			}
		}
	}
}
/*
연결 성공
연결 끊기
*/

 

MySQL

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

public class ConnectionExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);	
			
			System.out.println("연결 성공");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
					System.out.println("연결 끊기");
				} catch (SQLException e) {}
			}
		}
	}
}

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


TCP 채팅 프로그램 작동 원리

출처 : 이것이 자바다 유튜브 동영상 강의

클래스 용도
ChatServer -채팅 서버 실행 클래스
-ServerSocket을 생성하고 50001 포트에 바인딩
-ChatClient 연결 수락 후 SocketClient 생성
SocketClient -ChatClient와 1:1로 통신
ChatClient -채팅 클라이언트 실행 클래스
-ChatServer에 연결 요청
-SocketClient와 1:1로 통신
  • ChatServer 클래스에는 ServerSocket 객체가 50001번 포트로 들어오는 클라이언트 연결 요청을 수락한다.
  • SocketClient 객체는 연결 요청한 클라이언트 수만큼 생성된다.(스레드풀로 관리)
  • SocketClient의 Socket은 ChatClient 안에 있는 Socket과 1:1로 통신한다.

 


 

서버

ChatServer 클래스

필드

  • serverSocket : 클라이언트 요청 수락
  • threadPool : 스레드풀의 스레드 수를 100개로 제한(총 100개의 클라이언트가 동시 채팅 가능)
  • chatRoom : 통신용 SocketClient를 관리하는 동기화된 Map

 

메소드

  • start() : 채팅 서버가 시작할 때 가장 먼저 호출됨
    50001번 포트에 바인딩하는 ServerSocket 객체를 생성
    작업 스레드가 처리할 Runnable 구현 객체를 람다식으로 제공
    클라이언트가 연결 요청을 하면 수락하고 해당 클라이언트와 통신용 SocketClient를 생성
  • addSocketClient() : 연결된 클라이언트의 SocketClient를 chatRoom에 추가 
  • removeSocketClient() : 연결이 끊긴 클라이언트의 SocketClient를 chatRoom에서 제거
  • sendToAll() : JSON 메시지를 생성해 채팅방에 있는 모든 클라이언트에게 보내는 역할
    chatRoom.values()로 Collection<SocketClient>(맵의 키가 아닌 값)를 얻은 후 모든 SocketClient로 send() 메소드로 JSON을 보냄
    JSON은 아래와 같이 구성된다. 

  • stop() : 서버를 종료시키는 역할
    작업 스레드와 스레드풀을 종료시키지 않으면 프로세스가 종료되지 않으므로 메인 스레드, 작업 스레드, 스레드풀 모두 종료시킨다.

 

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.json.JSONObject;

public class ChatServer {
	//필드
	ServerSocket serverSocket;
	ExecutorService threadPool = Executors.newFixedThreadPool(100); //스레드풀의 스레드 개수를 100개로 제한
	Map<String, SocketClient> chatRoom = new Hashtable<>(); //해시 테이블은 동기화된 메소드를 제공해서 안전
	/*
	 String : chatName@IP
	 SocketClient : SocketClient 객체
	 */
	
	//메소드: 서버 시작
	public void start() throws IOException {
		serverSocket = new ServerSocket(50001);	//50001번 포트로 바인딩
		System.out.println( "[서버] 시작됨");
		
		Thread thread = new Thread(() -> { //서버에 연결 요청 수락을 대기하는 스레드
			try {
				while(true) {
					Socket socket = serverSocket.accept(); //연결 요청이 들어오면 수락
					SocketClient sc = new SocketClient(ChatServer.this, socket); // SocketClient 생성자에 이 클래스 객체와 socket 객체를 매개값으로 넘김
				}
			} catch(IOException e) {
			}
		});
		thread.start(); //스레드 시작
	}
	//메소드: 클라이언트 연결시 SocketClient 생성 및 추가
	public void addSocketClient(SocketClient socketClient) {
		String key = socketClient.chatName + "@" + socketClient.clientIp;
		chatRoom.put(key, socketClient); //chatRoom에 <chatName@IP, socketClient> 추가
		System.out.println("입장: " + key);
		System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n"); //현재 서버에 몇명이 들어왔는지 표시
	}

	//메소드: 클라이언트 연결 종료시 SocketClient 제거
	public void removeSocketClient(SocketClient socketClient) {
		String key = socketClient.chatName + "@" + socketClient.clientIp;
		chatRoom.remove(key); //chatRoom에서 해당 키 제거
		System.out.println("나감: " + key);
		System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
	}		
	//메소드: 모든 클라이언트에게 메시지 보냄
	public void sendToAll(SocketClient sender, String message) {
		JSONObject root = new JSONObject();
		root.put("clientIp", sender.clientIp); //JSON에 IP 추가
		root.put("chatName", sender.chatName); //JSON에 chatName 추가
		root.put("message", message);  //JSON에 보낼 메시지 추가
		String json = root.toString(); //String 타입으로 변환
		
		Collection<SocketClient> socketClients = chatRoom.values(); //chatRoom의 키가 아닌 값들만 뽑아옴
		for(SocketClient sc : socketClients) { //연결된 클라이언트들에게 
			if(sc == sender) continue; //발송자를 제외하고
			sc.send(json); //메시지 전송
		}
	}	
	//메소드: 서버 종료
	public void stop() {
		try {
			serverSocket.close();
			threadPool.shutdownNow();
			chatRoom.values().stream().forEach(sc -> sc.close()); //내부 반복자를 이용하여 연결된 클라이언트 모두 종료
			System.out.println( "[서버] 종료됨 ");
		} catch (IOException e1) {}
	}		
	//메소드: 메인
	public static void main(String[] args) {	
		try {
			ChatServer chatServer = new ChatServer();
			chatServer.start();
			
			System.out.println("----------------------------------------------------");
			System.out.println("서버를 종료하려면 q를 입력하고 Enter.");
			System.out.println("----------------------------------------------------");
			
			Scanner scanner = new Scanner(System.in);
			while(true) {
				String key = scanner.nextLine();
				if(key.equals("q")) 	break;
			}
			scanner.close();
			chatServer.stop();
		} catch(IOException e) {
			System.out.println("[서버] " + e.getMessage());
		}
	}
}

 


 

SocketClient 클래스

필드

  • chatServer : ChatServer 객체의 메소드를 호출하기 위함
  • socket : 연결을 끊을 때 필요
  • dis, dos : 문자열을 읽고 보내기 위한 보조스트림
  • clientIp : 클라이언트의 IP
  • chatName : 클라이언트의 대화명

 

메소드

  • receive() : 클라이언트가 보낸 JSON 메시지를 읽는 역할
    dis.readUTF로 JSON을 읽는다.
    JSON의 command가 incoming이라면 대화명을 읽고 chatRoom에 추가한다.
    command가 message라면 메시지를 읽고 모든 클라이언트에게 메시지를 보낸다.
  • send() : 연결된 클라이언트에게 JSON 메시지를 보내는 역할
  • close() : 클라이언트와 연결을 끊는 역할

 

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.json.JSONObject;

public class SocketClient {
	//필드
	ChatServer chatServer;
	Socket socket;
	DataInputStream dis; //송신
	DataOutputStream dos; //수신
	String clientIp;	
	String chatName;
	//생성자
	public SocketClient(ChatServer chatServer, Socket socket) {
		try {
			this.chatServer = chatServer;
			this.socket = socket;
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
			InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress(); //클라이언트의 IP와 포트 정보를 저장
			this.clientIp = isa.getHostString(); //IP정보를 저장
			receive();
		} catch(IOException e) {
		}
	}	
	//메소드: JSON 받기
	public void receive() {
		chatServer.threadPool.execute(() -> { //ChatServer의 스레드풀에 작업 전달
			try {
				while(true) {
					String receiveJson = dis.readUTF();	//JSON을 받아옴
					
					JSONObject jsonObject = new JSONObject(receiveJson);
					String command = jsonObject.getString("command"); //명령 내용에 따라 액션이 달라짐
					
					switch(command) {
						case "incoming": //첫 입장인 경우 incoming 을 받음
							this.chatName = jsonObject.getString("data"); //첫 입장일 경우 채팅네임이 data임 
							chatServer.sendToAll(SocketClient.this, "들어오셨습니다.");
							chatServer.addSocketClient(this);
							break;
						case "message":
							String message = jsonObject.getString("data"); //첫 입장이 아니면 메시지가 data임
							chatServer.sendToAll(SocketClient.this, message);
							break;
					}
				}
			} catch(IOException e) { //클라이언트와 연결이 끊겼을 경우
				chatServer.sendToAll(SocketClient.this, "나가셨습니다.");
				chatServer.removeSocketClient(SocketClient.this);
			}
		});
	}
	//메소드: JSON 보내기
	public void send(String json) {
		try {
			dos.writeUTF(json);
			dos.flush();
		} catch(IOException e) {
		}
	}	
	//메소드: 연결 종료
	public void close() {
		try { 
			socket.close();
		} catch(Exception e) {}
	}
}

 


 

클라이언트

ChatClient 클래스

필드

  • socket : 연결 요청과 연결을 끊을 때 필요
  • dis, dos : 문자열을 읽고 보내기 위한 보조 스트림
  • chatName : 클라이언트의 대화명

 

메소드

  • connect() : 채팅 서버(서버IP, 포트번호) : 에 연결을 요청하고 Socket을 필드에 저장한다.
  • receive() : 서버가 보낸 JSON 메시지를 읽는 역할
    readUTF()로 JSON을 읽고 파싱한다.
  • send() : 서버로 JSON 메시지를 보내는 역할
  • unconnect() : 서버와의 연결을 끊는 역할

 

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

import org.json.JSONObject;

public class ChatClient {
	//필드
	Socket socket;
	DataInputStream dis; //수신
	DataOutputStream dos; //송신
	String chatName;	
	//메소드: 서버 연결
	public  void connect() throws IOException {
		socket = new Socket("localhost", 50001); //연결할 서버의 내용 제공, localhost에 서버 IP를 넣어야함, 50001은 서버의 포트번호
		dis = new DataInputStream(socket.getInputStream());
		dos = new DataOutputStream(socket.getOutputStream());
		System.out.println("[클라이언트] 서버에 연결됨");		
	}	
	//메소드: JSON 받기
	public void receive() {
		Thread thread = new Thread(() -> { //메시지를 받는 스레드 생성
			try {
				while(true) {
					String json = dis.readUTF(); //JSON 수신
					JSONObject root = new JSONObject(json);
					String clientIp = root.getString("clientIp");
					String chatName = root.getString("chatName");
					String message = root.getString("message");
					System.out.println("<" + chatName + "@" + clientIp + "> " + message);
				}
			} catch(Exception e1) { //서어봐 연결이 끊겼을 시
				System.out.println("[클라이언트] 서버 연결 끊김");
				System.exit(0);
			}
		});
		thread.start();
	}	
	//메소드: JSON 보내기
	public void send(String json) throws IOException {
		dos.writeUTF(json);
		dos.flush();
	}	
	//메소드: 서버 연결 종료
	public void unconnect() throws IOException {
		socket.close();
	}	
	//메소드: 메인
	public static void main(String[] args) {		
		try {			
			ChatClient chatClient = new ChatClient();
			chatClient.connect(); //서버에 연결
			
			Scanner scanner = new Scanner(System.in);
			System.out.println("대화명 입력: ");
			chatClient.chatName = scanner.nextLine();
			
			JSONObject jsonObject = new JSONObject();
			jsonObject.put("command", "incoming"); //command 에 incoming (첫 입장) 내용 전달
			jsonObject.put("data", chatClient.chatName); //설정한 채팅네임 전달
			String json = jsonObject.toString();
			chatClient.send(json); //전달
			
			chatClient.receive(); //메시지 수신을 기다림
			
			System.out.println("--------------------------------------------------");
			System.out.println("보낼 메시지를 입력하고 Enter");
			System.out.println("채팅를 종료하려면 q를 입력하고 Enter");
			System.out.println("--------------------------------------------------");
			while(true) {
				String message = scanner.nextLine();
				if(message.toLowerCase().equals("q")) {
					break;
				} else {
					jsonObject = new JSONObject();
					jsonObject.put("command", "message"); //command 종류 : message
					jsonObject.put("data", message); //data에 보낼 메시지 전달
					json = jsonObject.toString();
					chatClient.send(json); //전달
				}
			}
			scanner.close();
			chatClient.unconnect();
		} catch(IOException e) {
			System.out.println("[클라이언트] 서버 연결 안됨");
		}
	}
}