Develop/Java

[11주차] SpringBoot - 2 (Spring Data JPA로 CRUD 구현)

SpruceMoon 2025. 11. 30. 22:52

0. Spring Data JPA

 - JPA (Java Persistence API): 자바의 표준 인터페이스로, 자바 객체를 DB에 저장할 때 따라야 하는 기본적인 규칙

 - Hibernate: JPA를 실제로 구현한 라이브러리로, 실제 DB와 통신하고 SQL을 만드는 '엔진' 역할을 함.

 - Spring Data JPA: Hibernate를 더 편하게 쓰기 위해 스프링이 한 번 더 감싸놓은 모듈이라고 함. 레포지토리 인터페이스만 만들면 구현체를 자동으로 생성해 준대요.

 

+) HikariCP
 - Spring Boot가 기본적으로 내장하고 있는 라이브러리로, 얘가 JDBC Connection Pool 역할을 하는데, 미리 DB 연결 객체를 여러 개 만들어두고 빌려주는 방식이라 성능이 매우 빠르고 안정적이라고 한다. 

+) JpaRepository를 상속받으면 기본 CRUD 외에도 메서드 이름만 잘 지으면 쿼리를 자동으로 만들어주는 쿼리 메서드 기능을 사용할 수 있다고 한다.

 

 - 영속성 컨택스트 : 쉽게 말해 엔티티를 영구 저장하는 환경이라는 뜻으로, 정확히는 애플리케이션과 DB 사이에 있는 중간 저장소(1차 캐시)라고 함. JPA는 데이터를 DB에 바로 넣지 않고, 잠시 이 중간 저장소에 보관해서 나중에 ID가 1인 User를 조회할 때, 여기에 있다면 DB에 쿼리를 보내지 않고 그냥 가져올 수 있어 성능이 향상됨! 쓰기 지연 기능도 있다고 함.

1. DB 및 프로젝트 준비 


 - ID : stdUser / PW : wkvmtlf2 / DB명 : Test_SpringDB 로 MariaDB를 준비했다.

 


 - 프로젝트명 : SpringBootDB
    - Spring Data JPA: DB 처리를 위한 핵심 라이브러리
    - MySQL/MariaDB Driver: DB와 연결하기 위한 드라이버
    - Spring Web: 웹 MVC 패턴 구현을 위해 필요

 - application.properties에 ID와 PW, 그리고 DB명을 입력해주었다.

spring.application.name=SpringBootDB

spring.datasource.hikari.jdbc-url=jdbc:mariadb://localhost:3306/Test_SpringDB
spring.datasource.hikari.username=stdUser 
spring.datasource.hikari.password=wkvmtlf2
spring.datasource.hikari.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.show-sql=true

 

 

2. Entity 클래스 생성 (User.java)

 - DB의 테이블과 1:1로 매핑되는 User 클래스를 생성함

package kr.ac.ewha.java2.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity //이 클래스가 DB 테이블과 매핑됨을 명시
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	private String name;
	private int age;
	public User() {}
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public User(int id, String name, int age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
	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; }
}

 - JPA는 기본 생성자가 필수다. 그 외에 데이터 삽입용(ID 제외), 수정/조회용(전체 필드) 생성자도 함께 만들어두면 편리하다고 한다..

3.  Repository 생성 (UserRepository.java)

 - DAO를 만들고 SQL을 직접 짤 필요 없이 Spring Data JPA에서는 인터페이스 하나면 구현이 가능함.
 - JpaRepository<User, Integer>를 상속받는 인터페이스를 생성하면 여러 메서드가 자동 생성되어 바로 사용이 가능.

package kr.ac.ewha.java2.repository;

import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;

import kr.ac.ewha.java2.entity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

}



4. 서비스(Service) 구현 (UserService.java)

 - 비즈니스 로직을 담당하는 서비스 클래스로, 컨트롤러와 리포지토리 사이에서 다리 역할을 함.

 - UserRepository를 주입받아 CRUD 기능을 구현함.

// 수업시간 보충용 서비스 클래스
package kr.ac.ewha.java2.service;

import java.util.List;
import org.springframework.stereotype.Service;
import kr.ac.ewha.java2.entity.User;
import kr.ac.ewha.java2.repository.UserRepository;

@Service // 이 클래스가 Spring의 서비스 컴포넌트임을 나타냄 (비즈니스 로직 담당)
public class UserService {

    // UserRepository를 주입받기 위한 필드 (데이터베이스 접근을 위한 인터페이스)
    private final UserRepository repository;

    // 생성자를 통한 의존성 주입 (Spring이 자동으로 주입해줌)
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    // CREATE: 새로운 사용자 생성 및 저장 (repository.save())
    public User create(User user) {
        return repository.save(user); // JPA의 save() 메서드를 통해 DB에 저장
    }

    // READ: 모든 사용자 목록 조회 (repository.findAll())
    public List<User> read() {
        return repository.findAll(); // 모든 사용자 엔티티를 리스트로 반환
    }

    // READ: ID로 사용자 조회 (없으면 null 반환) (repository.findById())
    public User readById(int id) {
        return repository.findById(id).orElse(null); // Optional에서 값이 없으면 null 반환
    }

    // UPDATE: ID로 사용자 조회 후 이름과 나이를 수정 (save())
    public User update(int id, User updatedUser) {
        User user = readById(id); // 기존 사용자 조회
        if (user == null) {
            return null; // 사용자가 없으면 null 반환
        }

        // 사용자 정보 업데이트
        user.setName(updatedUser.getName());
        user.setAge(updatedUser.getAge());
        return repository.save(user); // 수정된 사용자 저장
    }

    // DELETE: ID로 사용자 삭제 (성공 여부를 Boolean으로 반환) (repository.deleteById())
    public Boolean delete(int id) {
        if (!repository.existsById(id)) {
            return false; // 사용자가 없으면 false 반환
        }
        repository.deleteById(id); // 사용자 삭제
        return true; // 삭제 성공 시 true 반환
    }

}

 

 

5. 컨트롤러(Controller) 구현 (`SpringDBController.java`)

 - 사용자의 요청(URL)을 받아 적절한 서비스를 호출하고 결과를 반환하는 컨트롤러

 - 여기선 간단하게 HTML 문자열을 직접 리턴하거나 폼 데이터를 처리하는 방식을 사용한다고 함.

// 사용자 관련 요청을 처리하는 Spring MVC 컨트롤러 클래스
package kr.ac.ewha.java2.controller;

import org.springframework.web.bind.annotation.*;
import kr.ac.ewha.java2.entity.User;
import kr.ac.ewha.java2.service.UserService;
import org.springframework.stereotype.Controller;
import java.util.List;

@Controller // 이 클래스가 Spring의 컨트롤러임을 나타냄
@RequestMapping("/user") // 모든 요청 경로 앞에 "/user"를 붙임
public class SpringDBController {

    // 사용자 관련 비즈니스 로직을 처리하는 서비스 객체
    private final UserService service;

    // 생성자를 통해 UserService를 주입받음
    public SpringDBController(UserService service) {
        this.service = service;
    }

    // CREATE: 사용자 등록 처리
    @PostMapping("/submitForm") // POST 요청 "/user/submitForm" 처리
    @ResponseBody // 반환값을 HTTP 응답 본문으로 직접 출력
    public String create(@RequestParam("name") String name, @RequestParam("age") int age) {
        service.create(new User(name, age)); // 사용자 생성 및 저장
        return "<h3>등록 완료</h3><a href='/user-crud.html'>돌아가기</a>"; // 결과 HTML 반환
    }

    // READ: ID로 사용자 조회
    @GetMapping // GET 요청 "/user?id=..." 처리
    @ResponseBody
    public String readById(@RequestParam("id") int id) {
        User user = service.readById(id); // ID로 사용자 조회
        if (user == null) return "사용자 없음!"; // 없으면 메시지 반환
        // 사용자 정보 HTML로 반환
        return "<h3>사용자 정보</h3>" +
               "<p>ID: " + user.getId() + "</p>" +
               "<p>이름: " + user.getName() + "</p>" +
               "<p>나이: " + user.getAge() + "</p>" +
               "<a href='/user-crud.html'>돌아가기</a>";
    }

    // READ: 전체 사용자 목록 조회
    @GetMapping("/list") // GET 요청 "/user/list" 처리
    @ResponseBody
    public String list() {
        List<User> users = service.read(); // 모든 사용자 조회
        // HTML 테이블로 사용자 목록 구성
        StringBuilder html = new StringBuilder("<h3>전체 사용자 목록</h3><table border='1'><tr><th>ID</th><th>이름</th><th>나이</th></tr>");
        for (User u : users) {
            html.append("<tr><td>").append(u.getId()).append("</td><td>")
                .append(u.getName()).append("</td><td>")
                .append(u.getAge()).append("</td></tr>");
        }
        html.append("</table><br><a href='/user-crud.html'>돌아가기</a>");
        return html.toString(); // HTML 반환
    }

    // UPDATE: 사용자 정보 수정
    @PostMapping("/updateForm") // POST 요청 "/user/updateForm" 처리
    @ResponseBody
    public String update(@RequestParam("id") int id, 
                         @RequestParam("name") String name, 
                         @RequestParam("age") int age) {
        User result = service.update(id, new User(name, age)); // 사용자 수정
        if (result == null) {
            return "<h3>수정 실패: 사용자를 찾을 수 없습니다.</h3><a href='/user-crud.html'>돌아가기</a>"; // 실패 시 메시지
        }
        return "<h3>수정 완료</h3><a href='/user-crud.html'>돌아가기</a>"; // 성공 시 메시지
    }

    // DELETE: 사용자 삭제
    @PostMapping("/deleteForm") // POST 요청 "/user/deleteForm" 처리
    @ResponseBody
    public String delete(@RequestParam("id") int id) {
        boolean success = service.delete(id); // 사용자 삭제
        if (!success) {
            return "<h3>삭제 실패: 사용자를 찾을 수 없습니다.</h3><a href='/user-crud.html'>돌아가기</a>"; // 실패 시 메시지
        }
        return "<h3>삭제 완료</h3><a href='/user/list'>전체 목록 보기</a>"; // 성공 시 메시지
    }

}


 - '/user/submitForm': 사용자 등록 (POST)
 - '/user/list': 전체 목록 조회 (GET) - HTML 테이블 형태로 반환
 - '/user': ID로 상세 조회 (GET)
 - '/user/updateForm': 정보 수정 (POST)
 - '/user/deleteForm': 삭제 (POST)

6. 웹페이지

 - 입력을 받을 HTML 웹페이지를 생성하자!

1) input.html

<html>
<body>
	<meta charset="UTF-8">
	<form method="post" action="http://127.0.0.1:8080/user/submitForm" accept-charset="UTF-8">
	  <input type="text" name="name" value="이름" />
	  <input type="number" name="age" value="5" />
	  <button type="submit" >보내기</button>
	</form>
</body>
</html>

2) user-crud.html

<!DOCTYPE html>
<html lang="ko"> <!-- 문서의 언어를 한국어로 설정 -->
<head>
  <meta charset="UTF-8"> <!-- 문자 인코딩을 UTF-8로 설정 -->
  <title>사용자 CRUD - action 방식</title> <!-- 브라우저 탭에 표시될 제목 -->
</head>
<body>
  <h1>사용자 CRUD (action 방식)</h1> <!-- 페이지의 메인 제목 -->

  <!-- CREATE: 사용자 등록 폼 -->
  <h2>사용자 등록</h2>
  <form method="post" action="/user/submitForm"> <!-- POST 방식으로 /user/submitForm에 데이터 전송 -->
    이름: <input type="text" name="name" required /> <!-- 이름 입력 필드 (필수) -->
    나이: <input type="number" name="age" required /> <!-- 나이 입력 필드 (필수) -->
    <button type="submit">등록</button> <!-- 폼 제출 버튼 -->
  </form>

  <!-- READ (단일 조회): ID로 사용자 조회 -->
  <h2>사용자 조회 (ID로)</h2>
  <form method="get" action="/user"> <!-- GET 방식으로 /user에 ID 전송 -->
    ID: <input type="number" name="id" required /> <!-- 조회할 사용자 ID 입력 -->
    <button type="submit">조회</button> <!-- 조회 버튼 -->
  </form>

  <!-- READ (전체 조회): 모든 사용자 목록 조회 -->
  <h2>전체 사용자 조회</h2>
  <form method="get" action="/user/list"> <!-- GET 방식으로 /user/list 요청 -->
    <button type="submit">전체 목록 보기</button> <!-- 전체 목록 보기 버튼 -->
  </form>

  <!-- UPDATE: 사용자 정보 수정 -->
  <h2>사용자 수정</h2>
  <form method="post" action="/user/updateForm"> <!-- POST 방식으로 /user/updateForm에 데이터 전송 -->
    ID: <input type="number" name="id" required /> <!-- 수정할 사용자 ID 입력 -->
    새 이름: <input type="text" name="name" required /> <!-- 새 이름 입력 -->
    새 나이: <input type="number" name="age" required /> <!-- 새 나이 입력 -->
    <button type="submit">수정</button> <!-- 수정 버튼 -->
  </form>

  <!-- DELETE: 사용자 삭제 -->
  <h2>사용자 삭제</h2>
  <form method="post" action="/user/deleteForm"> <!-- POST 방식으로 /user/deleteForm에 ID 전송 -->
    ID: <input type="number" name="id" required /> <!-- 삭제할 사용자 ID 입력 -->
    <button type="submit">삭제</button> <!-- 삭제 버튼 -->
  </form>
</body>
</html>


 - 아직 잘 모르지만 JavaScript나 Thymeleaf 같은 템플릿 엔진을 쓰면 더 편하다고는 한다..

7. 실행 및 테스트 (SpringBootDbApplication.java)

 - 메인 어플리케이션 파일.

package kr.ac.ewha.java2;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import kr.ac.ewha.java2.entity.User;
import kr.ac.ewha.java2.service.UserService;

@SpringBootApplication
public class SpringBootDbApplication implements CommandLineRunner {

	@Autowired
	private UserService userService;
	public static void main(String[] args) {
		
		SpringApplication.run(SpringBootDbApplication.class, args);		
	  System.out.println("Project : Spring Boot DB start!");
	}

	@Override  //코드에서 테스트하기   
	public void run(String... args) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Spring DB is running");
		List <User> users = userService.read();
		//DB내용 불럭오기
		for (User u : users) {
			System.out.println(u.getId() + u.getName());
		}
		
		//코드에서 추가하기 
		//userService.create(new User("까만둥이",10));
		//User user =new User();
		//user.setName("유리유리");
		//user.setAge(5);
		//userService.create(user);
		
	}
}

 

8. 실행 결과

 - 삽입도 잘 되는데 전체 사용자 조회는 왜 안 되는지 모르겠다... Error가 나서 뜨질 않는다ㅠ



9. 소감

 원래는 UserDAO 클래스를 만들고 무언가를 막 짜야 했었는데 Spring을 사용하니 UserRepository 하나만 만들어놓으면 이 내부구현을 알아서 다 해주는 게 신기했다... 또 JPA를 사용하니 application.properties에 어떤 DB에 연결하고 ID가 뭐고 PW가 뭐고를 다 적어놓으면 되니까 JDBC를 쓸 때와는 달리 Connection 객체를 관리하지 않아도 되어서 편했다.. Spring이 정말 편리하다는 걸 그 전에 실습을 해보지 않았다면 몰랐을 것 같다... 문법이 익숙칠 않아 자꾸 뭔가를 타이핑하는 걸 까먹긴 했지만 그래도 획기적이네요. 익숙해지면 좋을 것 같다!