build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-webservices'
implementation 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // 추가
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
runtimeOnly 'com.h2database:h2' // 추가
implementation 'org.springframework.boot:spring-boot-h2console' // 추가
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
- Spring Boot 4.x 버전부터는 H2 콘솔을 사용하려면 h2console 의존성을 추가해야 한다.
application.properties
spring.application.name=spring-crud-back
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
- H2 인메모리 DB 사용
- 앱 실행 시 테이블 만들고 종료 시 정리
- 실행되는 SQL을 콘솔에 출력
- H2 콘솔 활성화
Post.java
package com.example.springcrudback.post;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Getter @Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
private String title;
@Setter
private String content;
public Post() {
}
public Post(String title, String content) {
this.title = title;
this.content = content;
}
}
- Spring Boot는 @Entity가 붙은 클래스를 자동으로 스캔해 JPA 엔티티로 인식한다.
- @GeneratedValue(strategy = GenerationType.IDENTITY): 기본키 값을 개발자가 직접 넣지 않고 DB가 자동으로 만든다.
PostRequest.java
package com.example.springcrudback.post;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class PostRequest {
private String title;
private String content;
public PostRequest() {
}
public PostRequest(String title, String content) {
this.title = title;
this.content = content;
}
}
PostRepository.java
package com.example.springcrudback.post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}
- JpaRepository<Post, Long>를 상속하면 기본 CRUD 메서드를 바로 사용할 수 있다.
- Spring Data JPA는 리포지토리를 인터페이스로 정의해 데이터 접근을 쉽게 해주고, 이름 기반 쿼리 생성도 지원한다.
PostService.java
package com.example.springcrudback.post;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
public Post create(PostRequest request) {
Post post = new Post(request.getTitle(), request.getContent());
return postRepository.save(post);
}
public List<Post> findAll() {
return postRepository.findAll();
}
public Post findById(Long id) {
return postRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Post with id " + id + " does not exist"));
}
public Post update(Long id, PostRequest request) {
Post post = postRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Post with id " + id + " does not exist"));
post.setTitle(request.getTitle());
post.setContent(request.getContent());
return postRepository.save(post);
}
public void delete(Long id) {
Post post = postRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Post with id " + id + " does not exist"));
postRepository.delete(post);
}
}
- PostController에서 정의했던 함수들을 PostService에 따로 정의했다.
PostController.java
package com.example.springcrudback.post;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController @RequestMapping("/posts")
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
@PostMapping @ResponseStatus(HttpStatus.CREATED)
public Post create(@RequestBody PostRequest request) {
return postService.create(request);
}
@GetMapping
public List<Post> findAll() {
return postService.findAll();
}
@GetMapping("/{id}")
public Post findById(@PathVariable Long id) {
return postService.findById(id);
}
@PutMapping("/{id}")
public Post update(@PathVariable Long id, @RequestBody PostRequest request) {
return postService.update(id, request);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
postService.delete(id);
}
}
실행 흐름
클라이언트 요청 → Controller → Service → Repository → H2 데이터베이스 → 결과 반환
- 어제 코드와 가장 큰 차이점은 Map이 아니라 DB에 저장한다는 점이다.
- 하지만 in-memory 모드로 사용하기 때문에 서버가 닫히면 저장된 데이터가 사라진다는 점은 같다.
- 데이터를 영구적으로 저장하기 위해서는 file-based 모드를 사용하거나 MySQL 등을 사용해야 한다.