[JAVA] 데이터베이스 입출력 (MySQL)

dev_swanim·2023년 4월 14일

JAVA 문법

목록 보기
7/10
post-thumbnail

JDBC란?

  • 자바는 DB와 연결해 데이터 입출력 작업을 할 수 있도록 JDBC(Java Database Connectivity) 라이브러리를 제공
    • DBMS 종류에 상관없이 동일하게 사용할수 있는 클래스와 인터페이스로 구성됨
  • JDBC 인터페이스를 통해 실제로 DB와 작업하는 것은 JDBC Driver(JDBC 인터페이스를 구현한 것)
Products설명
MySQL Server 8.0.25 - X64DBMS
MySQL Workbench 8.0.25 - X64DB 관리 및 개발을 위한 Client Tool
Connector/J 8.0.25 - X86JDBC Driver




1. DB 구성

MySQL Connection


Table 생성

스키마를 우클릭해 set as default schema를 선택

사용자 정보가 저장된 users 테이블

create table users (
	userid   		varchar(50)	  	primary key, 
	username		varchar(50)	  	not null,
	userpassword	varchar(50)	  	not null,
	userage			numeric(3)		not null,
	useremail		varchar(50)	  	not null
);

게시물 정보가 저장된 boards 테이블

create table boards (
    bno             int             primary key auto_increment,
    btitle          varchar(100)    not null,
    bcontent        longtext        not null,
    bwriter         varchar(50)     not null,
    bdate           datetime        default now(),
    bfilename       varchar(50)     null,
    bfiledata		longblob		null
);

계좌 정보가 저장될 accounts 테이블

create table accounts (
  ano       varchar(20)   primary key,
  owner     varchar(20)   not null,
  balance   numeric       not null
);

insert into accounts (ano, owner, balance) 
values ('111-111-1111', '하여름', 1000000);

insert into accounts (ano, owner, balance) 
values ('222-222-2222', '한겨울', 0);





2. DB 연결

  • 클라이언트 프로그램에서 DB와 연결하려면 해당 DBMS의 JDBC Driver가 필요하다.
  • 다음 네 가지 정보도 있어야 한다
    1. DBMS가 설치된 컴퓨터의 IP주소 (컴퓨터를 찾아가기 위해 필요)
    2. DBMS가 허용하는 포트 번호 (DBMS로 연결하기 위해)
    3. 사용자(DB계정) 및 비밀번호(DBMS는 여러 개의 DB를 관리하니 구분하기 위해 필요)
    4. 사용하고자 하는 DB이름

JDBC Driver

  • JDBC가 설치된 경로 C:\Program Files (x86)\MySQL\Connector J 8.0

DB 연결 코드

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

public class Main {
    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/javapractice",
                    "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) {}
                }
            }
        }
}

➕ jdk 1.6부터는 getConnection 호출할 때 드라이버가 자동으로 로드되어 Class.forName 작성을 안 해도 된다

⚠️ "jdbc:mysql://localhost:3306/javapractice""jdbc:mysql://ip주소:포트/DB명"

DB명이라는 것에 조심하자!






3. 데이터 저장

JDBC를 이용해 INSERT문을 실행

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 conn = null;
        try {
            //JDBC Driver 등록
            Class.forName("com.mysql.cj.jdbc.Driver");

            //연결하기
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/javapractice",
                    "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) {}
                }
            }
    }
}







4. 데이터 수정

  • JDBC를 이용해 UPDATE문을 실행
import java.io.FileInputStream;
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 conn = null;
        try {
            //JDBC Driver 등록
            Class.forName("com.mysql.cj.jdbc.Driver");

            //연결하기
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/javapractice",
                    "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) {
                }
            }
        }
    }
}






5. 데이터 삭제

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 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) {}
                }
            }
        }
}






6. 데이터 읽기

  • SQL문이 INSERT, UPDATE, DELETE일 경우에는 executeUpdate() 메소드 호출
  • 데이터를 가져오는 SELECT문일 경우는 executeQuery() 메소드 호출

ResultSet 구조

  • SELECT 문에 기술된 컬럼으로 구성된 행의 집합
  • 커서가 있는 행의 데이터만 읽을 수 있음. 초기 시작은 beforeFirst에 위치해 있으므로 next()을 이용해 first 행을 읽으면 됨

데이터 행 읽기

만약 SELECT 문에 연산식이나 함수 호출이 포함되어 있다면 컬럼 이름 대신에 컬럼 순번으로 읽어
야 한다

  • 컬럼 이름으로 읽기 : String userId = rs.getString("userid");
  • 컬럼 순번으로 읽기 : String userId = rs.getString(1);

사용자 정보 읽기

users 테이블의 한 개의 행(사용자)을 저장할 User 클래스

import lombok.Data;

@Data //Constructor, Getter, Setter, hashCode(), equals(), toString() 자동 생성
public class User {
    private String userId;
    private String userName;
    private String userPassword;
    private int userAge;
    private String userEmail;
}

users 테이블에서 userid가 winter인 사용자 정보를 가져오는 코드

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 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 테이블의 1개 행(게시물)을 저장할 Board 클래스

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

@Data //Constructor, Getter, Setter, hashCode(), equals(), toString() 자동 생성
public class Board { 
    private int bno;
    private String btitle;
    private String bcontent;
    private String bwriter;
    private Date bdate;
    private String bfilename;
    private Blob bfiledata;
}

boards 테이블에서 bwriter가 winter인 게시물 정보를 가져오는 코드

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();
                    Appendix
                    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) {}
                }
            }
        }
}






7. 트랜잭션 처리

  • 트랜잭션(transaction) : 기능 처리의 최소 단위
    • 소작업들이 모두 성공하거나 모두 실패해야 한다
    • ex. 계좌이체는 출입금 작업으로 구성된 트랜젝션. 출금과 입금 작업 중 하나만 성공할 수 없으며, 모두 성공하거나 모두 실패해야 한다
  • 트랜잭션 처리를 위해 DB는 커밋(commit)과 롤백(rollback)을 제공
    • 커밋 : 내부 작업을 모두 성공 처리
    • 롤백 : 실행 전으로 돌아간다는 의미에서 모두 실패 처리

⚠️ JDBC에서는 INSERT, UPDATE, DELETE 문을 실행할 때마다 자동 커밋이 일어난다. 이 기능은
계좌 이체와 같이 두 가지 UPDATE 문을 실행할 때 문제가 된다. 출금 작업이 성공되면 바로 커밋
이 되기 때문에 입금 작업의 성공 여부와 상관없이 출금 작업만 별도 처리된다.
따라서 JDBC에서 트랜잭션을 코드로 제어하려면 자동 커밋 기능을 꺼야 한다

→ 자동 커밋 설정 여부는 Connection의 setAutoCommit() 메소드로 설정


accounts 테이블에서 111-111-1111계좌에서 222-222-2222 계좌로 만 원을 이체하기 위한 트랜잭션 처리 코드

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, "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) {
                }
            }
        }
    }
}



커넥션 풀(Connection Pool)

  • 커넥션 풀이란? : 일정량의 Connection을 미리 생성시켜 놓고, 서버에서 클라이언트 요청을 처리할 때 Connection을 제공해주고 다시 반환받는 것
    • 다수의 클라이언트 요청을 처리하는 서버 프로그램은 대부분 커넥션 풀을 사용

    • 생성된 Connection을 재사용할 수 있기에 DB 연결시간을 줄일 수 있고, 전체 Connnection을 수를 관리할 수도 있다

    • 불특정 다수의 클라이언트 요청을 처리하는 서버 프로그램에서는 필수 기능 중 하나




📚참고 문헌

이것이 자바다(신용권, 임경균 지음)

profile
데이터와 백엔드를 공부하고 있습니다😌

0개의 댓글