Develop/Java

[9주차] DB & JDBC

SpruceMoon 2025. 11. 16. 22:46

[1] DB 개념 정리

1. 데이터베이스와 DBMS + 관계형 데이터베이스

1) 데이터베이스

 : 여러 응용 시스템이 공유할 수 있도록 통합, 저장된 공용 데이터의 집합

 : 저장, 검색, 갱신을 효율적으로 수행할 수 있도록 데이터를 고도로 조직화해 저장

 

2) 관계형 데이터베이스(RDBMS) : 키(key)와 값(value)의 관계를 테이블 형태로 표현하는 모델

RDBMS 구조

 - 릴레이션 : 하나의 개체에 관한 데이터를 2차원 표(2차원 테이블 구조) 형태로 저장한 것

 - 속성 (Attribute): 릴레이션의 '열', 파일관리시스템의 필드에 해당

 - 투플 (Tuple): 릴레이션의 '행', 파일관리시스템의 레코드에 해당

 - 도메인 (Domain) : 하나의 속성이 가질 수 있는 모든 값의 범위 또는 데이터타입 (ex. int, char, float 등)

2. SQL과 JDBC

 - SQL : RDBMS에서 쓰는 쿼리용 언어로, DB 스키마 생성, 자료의 검색, 관리, 수정, DB 객체 접근 관리를 위해 고안됨.

 - JDBC(Java DataBase Connectivity) : RDBMS에서 저장된 데이터를 접근/조작할 수 있게 하는 JAVA API

     ㄴ 다양한 DBMS에 대해 일관된 API로 데이터베이스 연결, 검색, 수정, 관리 등이 가능

2. 기본 SQL 문법 (CRUD)

 - CRUD : 데이터를 다루는 가장 기본적인 4가지 기능

 

 - Create (생성): INSERT 문을 사용해 새 데이터를 테이블에 추가

    ㄴ INSERT INTO student (hakbun, name, dept) VALUES ('2024001', '김이화', '컴퓨터공학과');

 - Read (읽기): SELECT 문을 사용해 테이블의 데이터를 조회

    ㄴ SELECT * FROM student WHERE dept = '컴퓨터공학과';

 - Update (수정): UPDATE 문을 사용해 기존 데이터를 수정

    ㄴ UPDATE student SET name = '박이화' WHERE hakbun = '2024001';

 - Delete (삭제): DELETE 문을 사용해 기존 데이터를 삭제

    ㄴ DELETE FROM student WHERE hakbun = '2024001';


[2] JDBC 흐름 정리

 - JDBC(Java DataBase Connectivity) : 자바 애플리케이션이 DB에 연결하고 SQL 문을 실행할 수 있게 해주는 표준 API이므로, 덕분에 DB 종류가 바뀌더라도 자바 코드는 거의 변경할 필요 없다는 장점 존재

JDBC 프로그래밍 핵심 5단계>

 1) JDBC 드라이버 로드

    - 사용하려는 DB의 JDBC 드라이버 클래스를 JVM에 로드

    - JDBC 4.0 이후에는 jar 파일이 classpath에 포함되어 있어면 생략 가능. (but 명시적으로 작성하는 경우도 있음.)

try { // MySQL 8.x 버전 기준 드라이버 클래스
	Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
CREATE TABLE user (
	id BIGINT PRIMARY KEY, -- 숫자 형식의 고유 ID
	name VARCHAR(50),
	age INT
); -- 예제 데이터 삽입
INSERT INTO user (id, name, age) VALUES (1, '신짱구', 7);
INSERT INTO user (id, name, age) VALUES (2, '신짱아', 3);

 (+ DB 테이블 준비)

 

 2) 데이터베이스 연결 (Connection)

    - DriverManager로 DB 서버 URL, 계정 정보로 실제 연결을 맺고 성공 시 Connection 객체를 반환받음

// DB 서버 주소
String url = "jdbc:mysql://localhost:3306/mydatabase"; //db
String user = "dbuser"; // 계정
String password = "dbpassword"; // 비밀번호
Connection conn = null;
try {
	conn = DriverManager.getConnection(url, user, password);
	System.out.println("DB 연결 성공!");
} catch (SQLException e) {
	System.out.println("DB 연결 실패...");
	e.printStackTrace();
}

 

 3) SQL문 실행 (Statement / PreparedStatement)

    - Connection 객체로부터 SQL을 실행할 PreparedStatement 객체를 생성

    - PreparedStatement는 SQL 인젝션 공격을 방어할 수 있고 성능도 좋아 사용 강력 권장!
    - .executeUpdate() 메서드 : INSERT, UPDATE, DELETE 쿼리
    - .executeQuery() 메서드 : SELECT 쿼리 실행

// PreparedStatement 사용 예시 : user테이블에 id, name, age를 추가하는 예
String sql = "INSERT INTO user (id, name, age) VALUES (?, ?, ?)”;
PreparedStatement pstmt = conn.prepareStatement(sql);
// ?에 값 바인딩 (첫 번째 ?는 인덱스 1)
pstmt.setString(1, “user01”);
pstmt.setString(2, “신짱구");
pstmt.setString(3, 5);
int result = pstmt.executeUpdate(); // INSERT, UPDATE, DELETE 실행

 

 4) 결과 처리 (ResultSet)

    - SELECT 문의 실행 결과는 ResultSet 객체로 반환됨.

    - next() 메서드를 while 문에서 호출하여 한 행(row)씩 데이터를 순회하며 읽어옴.

// user테이블을 PreparedStatement로 회원 조회
String sql = "SELECT id, name, age FROM user WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, “user01”);

ResultSet rs = pstmt.executeQuery(); // SELECT 실행
if (rs.next()) { // 조회된 데이터가 있다면
	String name = rs.getString("name"); // "name" 컬럼의 값을 String으로 가져옴
	String age = rs.getInt(“age");
	System.out.println("이름: " + name + ", 나이: " + age);
}

 5) 자원 해제 (Close)

    - 사용한 ResultSet, PreparedStatement, Connection 객체는 시스템 자원 누수를 막기 위해 사용한 역순으로 닫아주기.

    - try() 안의 리소스는 예외 발생하더라도 finally 없이 자동으로 close() 호출되어 편리함!

String sql = "SELECT id, name FROM user";
try ( Connection conn = DriverManager.getConnection(url, user, password);
	PreparedStatement pstmt = conn.prepareStatement(sql);
	ResultSet rs = pstmt.executeQuery()) {
	// rs.next()가 false를 반환할 때까지 반복
	while (rs.next()) {
		System.out.println("아이디: " + rs.getString("id") + ", 이름: " + rs.getString("name"));
	}
} catch (SQLException e) {
	e.printStackTrace();
} // try 블록을 벗어나는 순간 rs, pstmt, conn이 자동으로 close() 됨!

 

 

3. 실습 복기 (student 테이블)

 - MariaDB 서버 + HeidiSQL을 이용

 - test DB 안에 student 테이블 생성 -> '번호', '학과', '학번', '이름' 속성 추가 / '학번'에는 고유키(Unique Key) 속성 부여

 - HeidiSQL의 '데이터' 탭에서 직접 or 이클립스에서 JDBC 코드를 작성하여 student 테이블에 INSERT와 SELECT 쿼리를 실행 등이 가능.

package student;

public class Student {
	private int no;	
	private String dept;
	private String sid;
	private String name;

	public Student() {};
	public Student(String dept, String sid, String name) {
		this.dept = dept;
		this.sid = sid;
		this.name = name;
	}

	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public String getDept() {
		return dept;
	}
	public void setDept(String dept) {
		this.dept = dept;
	}
	public String getSid() {
		return sid;
	}
	public void setSid(String sid) {
		this.sid = sid;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}
package student;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// 학생 정보를 DB에 삽입, 조회
public class StudentDao {
	private final DatabaseConnector connector;

	public StudentDao(DatabaseConnector connector) {
		this.connector = connector;
	}

	// 학생 정보를 INSERT 하는 메서드 : no, dept, stdID, name 필드이름과 같아야 함
	public void insertStudent(Student student) {
		String sql = "INSERT INTO student ( dept, stdID, name) VALUES (?, ?, ?)";

		try (Connection conn = connector.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS)) {
	
			pstmt.setString(1, student.getDept());
			pstmt.setString(2, student.getSid());
			pstmt.setString(3, student.getName());
			
			pstmt.executeUpdate();

			try (ResultSet generatedKeys = pstmt.getGeneratedKeys()) {
				if (generatedKeys.next()) {
					int no = generatedKeys.getInt(1); //첫번째 컬럼 자동증가
					student.setNo(no);
					System.out.println(no +" 학생 정보가 추가되었습니다.");
				}
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	// 학생 정보 수정
	public void updateStudent(Student student) {
		String sql = "UPDATE student SET dept = ?, stdID = ?, name = ? WHERE no = ?";

		try (Connection conn = connector.getConnection();
				PreparedStatement pstmt = conn.prepareStatement(sql)) {

			pstmt.setString(1, student.getDept());
			pstmt.setString(2, student.getSid());
			pstmt.setString(3, student.getName());
			pstmt.setInt(4, student.getNo()); // 대상 학생의 no

			int rows = pstmt.executeUpdate();
			if (rows > 0) {
				System.out.println("학생 정보가 수정되었습니다.");
			} else {
				System.out.println("해당 no의 학생이 존재하지 않습니다.");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	// 학생 정보 삭제
	public void deleteStudentByNo(int no) {
		String sql = "DELETE FROM student WHERE no = ?";

		try (Connection conn = connector.getConnection();
			PreparedStatement pstmt = conn.prepareStatement(sql)) {

			pstmt.setInt(1, no);
			int rows = pstmt.executeUpdate();
			if (rows > 0) {
				System.out.println("학생 정보가 삭제되었습니다.");
			} else {
				System.out.println("해당 no의 학생이 존재하지 않습니다.");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	// DB에서 모든 학생 정보를 가져오기
	public List<Student> getAllStudents() {
		List<Student> students = new ArrayList<>();
		String sql = "SELECT * FROM student order by no ASC ";

		try (Connection conn = connector.getConnection();
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(sql)) {

			while (rs.next()) {
				Student s = new Student();
				s.setNo(rs.getInt("no"));
				s.setDept(rs.getString("dept"));
				s.setSid(rs.getString("stdID")); 
				s.setName(rs.getString("name"));
				students.add(s);
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		return students; // 조회한 학생 리스트 반환
	}
}
package user;

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

public class DatabaseConnector {
  private static final String URL = "jdbc:mysql://localhost:3306/test";  
  private static final String USER = "stdUser";
 	private static final String PASS = "wkvmtlf2";

	
	public static Connection getConnection() throws SQLException {
		return DriverManager.getConnection(URL, USER, PASS);
	}
	public DatabaseConnector() {}
	
}
package student;

import java.util.List;

public class StudentTest {
	public static void main(String[] args) {
        DatabaseConnector connector = new DatabaseConnector();
        StudentDao dao = new StudentDao(connector);
        
        dao.insertStudent(new Student("떡잎마을학과","20240129","철수"));
        List<Student> students = dao.getAllStudents();

        Student student = new Student();
        student.setNo(4);
        student.setDept("컴퓨터공학과");
        student.setSid("2466048");
        student.setName("장은서");
        dao.updateStudent(student);      

        dao.deleteStudentByNo(6);

        for(Student s : students) {
            System.out.printf("No: %d, 학과: %s , 학번: %s 이름: %s\n", s.getNo(), s.getDept(), s.getSid(), s.getName());
        }
    }
}

4. 애플리케이션 권장 구조 정리 : MVC + DAO + DTO + Service 

 - JDBC 코드는 설계를 어떻게 하느냐에 따라 프로젝트 품질이 크게 달라짐.

 - SOLID 원칙과 디자인 패턴을 적용하여 역할을 분리하는 것 중요

DTO
(Model)
Student 학생 정보 객체
 : DTO (Data Transter Object)
택배상자처럼 데이터를 담아 다른계층으로 전달. 가볍고 단순해야 함.
DAO StudentDAO DB 접근 전담 CRUD 처리 클래스 (JDBC 사용해 SQL 실행) DAO 인터페이스를 도입할 수도 있음
Service StudentService 비즈니스 로직 처리, DAO 클래스의 CRUD처리 메소드 호출. 예) 학생조회하기 메소드는 DAO의 read 처리용메소드를 호출하도록 구현.
View StudentView 사용자 입출력 처리(Scanner, System.out, JLabel 등) 시각적인 UI를 만들지 않는 경우, Contoller나 Main가 View역할을 포함할 수 있으나 분리하는 게 좋음.
Controller StudentController 사용자 요청 처리  
Main DBManager 프로그램 시작점  
package user;

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

public class DatabaseConnector {
  private static final String URL = "jdbc:mysql://localhost:3306/test";  
  private static final String USER = "stdUser";
 	private static final String PASS = "wkvmtlf2";

	
	public static Connection getConnection() throws SQLException {
		return DriverManager.getConnection(URL, USER, PASS);
	}
	public DatabaseConnector() {}
	
}
package user;

import java.util.Optional;

public class Main {
	public static void main(String[] args) {
		try {
			UserRepository userRepository = new UserDao();
			UserService service = new UserService(userRepository);

			service.createUser("김철수", 7);
			Optional<User> foundUser = service.getUser(1);

			foundUser.ifPresentOrElse(user->System.out.println("사용자 찾음 : "+user.getName()), ()->System.out.println("사용자 없음"));
		
			service.updateUser(5,"흰둥이", 4);

			for (User u : service.getAllUsers()) {
				System.out.println(u.getId() + " / " + u.getName() + " / " + u.getAge());
			}

			service.deleteUser(6);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
package user;
public class User {
	private int id;
	private String name;
	private int age;

	// 전체 필드 생성자 (UPDATE, SELECT 시 사용)
	public User(int id, String name, int age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}

	// ID 없이 생성자 (INSERT 시 사용)
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}

	// Getters and Setters
	public int getId() { return id; }
	public void setId(int id) { this.id = id; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	public int getAge() { return age; }
	public void setAge(int age) { this.age = age; }


}
package user;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class UserDao implements UserRepository{

    public void create(User user) throws SQLException {
        String sql = "INSERT INTO user (id, name, age) VALUES (?, ?, ?)";
        try (Connection conn = DatabaseConnector.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setInt(1, user.getId());
            stmt.setString(2, user.getName());
            stmt.setInt(3, user.getAge());
            stmt.executeUpdate();
        }
    }

    public User read(int id) throws SQLException {
        String sql = "SELECT * FROM user WHERE id = ?";
        try (Connection conn = DatabaseConnector.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setInt(1, id);
            ResultSet rs = stmt.executeQuery();
            if (rs.next()) {
                return new User(rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
            }
        }
        return null;
    }

    public void update(User user) {
        String sql = "UPDATE user SET name = ?, age = ? WHERE id = ?";
        try (Connection conn = DatabaseConnector.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setString(1, user.getName());
            stmt.setInt(2, user.getAge());
            stmt.setInt(3, user.getId());
            stmt.executeUpdate();
        } catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }

    public void delete(int id) throws SQLException {
        String sql = "DELETE FROM user WHERE id = ?";
        try (Connection conn = DatabaseConnector.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setInt(1, id);
            stmt.executeUpdate();
        }
    }

	@Override
	public User save(User user) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Optional<User> findById(int id) {
		// TODO Auto-generated method stub
		return Optional.empty();
	}

	@Override
	public List<User> findAll() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void deleteById(int id) {
		// TODO Auto-generated method stub
		
	}
}
package user;

import java.util.List;
import java.util.Optional;

public interface UserRepository {
    User save(User user) ;  //생성 (Create)
    //void save(User user) ;
    Optional<User> findById(int id) ;  // ID로 조회 (Read)
    List<User> findAll() ;    // 전체 조회 (Read)
    void update(User user) ;// 수정 (Update)
    void deleteById(int id) ; // 삭제 (Delete)
}
package user;
import java.util.List;
import java.util.Optional;

public class UserService {
    // 구체 클래스(UserDao)가 아닌 인터페이스(UserRepository)에 의존
    private final UserRepository userRepository;

    // 생성자를 통한 의존성 주입 (DI)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(String name, int age) {
        User newUser = new User(name, age);
        return userRepository.save(newUser);
    }

    public Optional<User> getUser(int id) {
        return userRepository.findById(id);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public void updateUser(int id, String newName, int newAge) {
        User userToUpdate = new User(id, newName, newAge);
        userRepository.update(userToUpdate);
    }

    public void deleteUser(int id) {
        userRepository.deleteById(id);
    }
}

5. 후기

 - 초기 설정에 오류가 좀 났어서 해결하느라 시간을 좀 썼음. MariaDB 설치 시 root 비번을 잊지 않도록 하고 외부에서 접속하는 것도 막아두는 거 일단 기억하고 있음.

 - try-catch-finally 구문으로 자원 해제 코드를 반복적으로 작성하는 것이 번거로웠는데, try-with-resources 쓰는 게 복잡해는 보여서 적응하는 데 좀 걸렸지만 익숙해지니 나름 편한 듯.

 - DAO DTO 등의 구조가 어려웠음...

 - 이번에 하는 자프실2 기말대체과제 프로젝트에 적용하면 좋을 개념들이 몇몇개 보여서 보람찼음.