Spring Boot REST API를 통한 CRUD를 만들고자 한다. 스프링 개발자라면 필수로 익숙해야 할 것이다.
이번의 CRUD는 Jpa와 mysql를 이용하여 실제 데이터베이스에 적용할 것이다.
먼저 필요한 의존성을 추가해보자.
의존성 추가, application.properties 설정
bulid.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'mysql:mysql-connector-java'
runtimeOnly("com.mysql:mysql-connector-j")
// Spring Data JPA
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
- org.springframework.boot:spring-boot-starter-data-jpa: 스프링 부트에서 JPA를 사용하기 위한 스타터읻. 데이터베이스와의 상호 작용을 위한 ORM(Object-Relational Mapping) 기능을 사용할 수 있다.
- org.springframework.boot:spring-boot-starter-web: 스프링 부트에서 웹 애플리케이션을 개발하기 위한 필수 스타터이다. 이를 추가함으로써 웹 애플리케이션을 위한 기본적인 설정과 웹 관련 기능들을 사용하게 해준다..
- mysql:mysql-connector-java: MySQL 데이터베이스와의 연동을 위해 필요한 JDBC 드라이버이다.
application.properties
## MYSQL ###
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username= 아이디
spring.datasource.password= 비밀번호
spring.datasource.url=jdbc:mysql://localhost:3306/데이터베이스 이름
### JPA ###
spring.jpa.show-sql=true // 트랜잭션시 쿼리를 콘솔에 표시
spring.jpa.hibernate.ddl-auto= update // 스키마 생성전략
properies에는 MYSQL의 연동과 JPA 사용을 위한 기본설정만 해주었다.
패키지 구조
controller : 컨트롤러 클래스를 모아두는 패키지이다.
service : 서비스 클래스를 모아두는 패키지이다.
repository : 레포지토리 클래스를 모아두는 패키지이다.
domain : DB 테이블 컬럼과 동일한 필드를 가진 클래스를 모아두는 패키지 이다.
dto : Data Transfer Object, dto 클래스를 모아두는 패키지이다.
구현할 기능
구현할 기능은 게시글 작성, 읽기, 수정, 삭제이다.
Domain
package com.example.CRUD.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
}
도메인은 DB의 테이블과 매핑되는 클래스이다.
@Entity : JPA(Java Persistance API)에서 엔터티 클래스임을 나타내고 DB에 액세스 할 때 사용된다.
@ID : privamry key임을 나타낸다.
@GeneratedValue : 이 어노테이션은 식별자의 값을 자동으로 생성하는 전략이다. strategy는 식별자 값을 생성하는 전략이다. GenerationType.IDENTITY는 DB에 의해 식별자 값이 자동으로 생성되도록 하는 전략이다.
따라서 JPA를 사용하여 DB와 매핑되는 엔티티 클래스이다.
DTO
package com.example.CRUD.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PostDTO {
private String title;
private String content;
}
DTO는 주로 데이터를 전송하기 위한 객체로 사용된다. 데이터를 전송하거나 전달하기 위한 정보만 포함되어 있다.
DTO를 사용하는 이유는 필요한 정보만을 포함해 불필요한 데이터 전송을 피하고, 각 전송 계층 간에 데이터를 전송 할 때 DTO를 사용하면 데이터를 캡슐화할 수 있고, 전송할 수 있으므로 안정성이 높다.
따라서 클라이언트, 서버간의 데이터를 전송 및 전달의 중간(?)의 객체이다.
Repositroy
package com.example.CRUD.repository;
import com.example.CRUD.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}
Post 엔티티를 처리하며 기본 키가 Long 타입이다. JPA 인터페이스를 상속받아서 CRUD 작업을 가능케 한다.
별도의 구현 코드가 없이도 JpaRepositroy가 제공하는 메서드들을 사용하여 DB와 상호작용을 할 수 있다.
별도의 쿼리를 생성도 가능하다.
메서드 이름 규칙에 따른 쿼리 생성
package com.example.CRUD.repository;
import com.example.CRUD.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByTitle(String title);
List<Post> findByTitleAndContent(String title, String contnet);
//JPQL
@Query("SELECT p FROM Post p WHERE p.content = ?1")
List<Post> findByContent(String content);
//nativeQuery
@Query(value = "SELECT * FROM posts WHERE content = ?1", nativeQuery = true)
List<Post> findByContentNative(String content);
}
메서드 이름을 분석하여 쿼리를 생성하는 기능을 제공한다. 기본적으로 findBy가 기본이며, and, or orderby 등의 접두사를 사용할 수 있다. 또한 @Query 어노테이션을 사용한 JPQL이나 네이티브 쿼리로 정의할 수 있다. 이는 복잡한 쿼리를 작성해야할 떄 주로 사용된다.
해당 메소드들은
findBytitle : title을 기준으로 검색한다.
findByTitleAndContent : title과 content를 and 연산 기준으로 검색한다.
findByContent, findByContentNative : content의 매개변수로 전달된 content값이 일치하는지 확인하고 검색한다.
(?1은 JPQL 바인딩 매개변수를 나타내며, 메서드 첫 번 쨰 매개변수 content에 해당된다.)
JPQL과 네이티브 쿼리의 차이점
JPQL : JPQL은 객체 지향 쿼리 언어로 엔티티와 필드 이름을 사용하여 쿼리를 작성한다. SQL과 유사하지만 테이블 및 컬럼 이름 대신 엔티티 및 필드 이름을 사용한다. 또한 JPA 구현체에 의해 자동으로 데이터베이스에 맞게 변환된다. 독립적이며, 코드를 데이터베이스를 변경하지 않고 재사용할 수 있다. 그리고 엔티티와 필드 이름을 사용하기 때문에 오타가 발생할 가능성이 매우 적다. JPA 구현체가 JPQL을 분석하고 데이터베이스에 맞게 변환하므로 안정성이 높다.
네이티브 쿼리 : 네이티브 쿼리는 기존의 SQL 문법을 사용하여 직접 SQL 쿼리를 작성한다. 쿼리는 데이터베이스에 대한 원시 SQL 쿼리로 해석된다. 또한 직접 SQL을 사용하기 때문에 데이터베이스에 종속적이다. 따라서 데이터베이스 환경을 변경할 때 네이티브 쿼리도 함꼐 변경해야 한다는 번거러움이 있으며 오타나 문법 오류 등이 발생 할 수 있다. 데이터베이스에 직접 의존하기 때문에 데이터베이스 스키마 변경 시에 네이티브 쿼리도 함께 변경해야 한다.
JPQL은 객체 지향적이고 독립적으로 쿼리를 작성하기 위해 사용된다. 어떻게 보면 JPQL이 훨씬 좋아보이지만 일반적으로 성능 최적화 등의 특별한 경우에는 네이티브 쿼리를 사용한다고 한다.
Service
package com.example.CRUD.service;
import com.example.CRUD.domain.Post;
import com.example.CRUD.dto.PostDTO;
import java.util.List;
public interface PostService {
List<Post> getAllPosts();
Post getPostById(Long id);
Post createPost(PostDTO postDTO);
Post updatePost(Long id, Post updatedPost);
void deletePost(Long id);
}
서비스 클래스를 구현하기위한 인터페이스이다. 게시글 생성, 읽기, 수정, 삭제 기능을 정의한다. Service 클래스는 이를 상속받아서 구현한다. 인터페이스를 사용하는 이유는 인터페이스를 상속받은 클래스는 해당 인터페이스에서 정의된 메소드들은 모두 구현을 해야만 하는 제약이 있으며 코드의 일관성과 안정성을 제공한다.
package com.example.CRUD.service;
import com.example.CRUD.domain.Post;
import com.example.CRUD.dto.PostDTO;
import com.example.CRUD.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PostServiceImpl implements PostService{
@Autowired
private PostRepository postRepository;
@Override
public List<Post> getAllPosts() {
return postRepository.findAll();
}
@Override
public Post getPostById(Long id) {
return postRepository.findById(id)
.orElseThrow(() -> new RuntimeException("포스트를 찾을 수 없습니다."));
}
@Override
public Post createPost(PostDTO postDTO) {
Post post = new Post();
post.setTitle(postDTO.getTitle());
post.setContent(postDTO.getContent());
return postRepository.save(post);
}
@Override
public Post updatePost(Long id, PostDTO postDTO) {
return postRepository.findById(id)
.map(post -> {
post.setTitle(postDTO.getTitle());
post.setContent(postDTO.getContent());
return postRepository.save(post);
})
.orElseThrow(() -> new RuntimeException("포스트를 찾을 수 없습니다."));
}
@Override
public void deletePost(Long id) {
postRepository.deleteById(id);
}
}
PostService를 구현한 PostServiceImpl 클래스이다. 이클래스는 컨트롤러에 요청에 왔을때 서비스 로직을 따로 분리 한 클래스이며 CRUD를 구현하였다.
구현체에 impl을 붙이는 이유는 구현할 클래스가 하나이거나 다른 수정사항이 없을때 관례상 Impl을 붙인다. 스프링 프레임워크에서 구현 클래스를 자동으로 검색하고 인식하는 경우가 있는데 Impl을 붙이면 프레임워크가 자동으로 검색하기도 쉽고 코드의 가독성을 높이며 식별하기 쉽게 해준다.
@Service : 어노테이션은 스프링 빈으로 등록되어 컨테이너에 의해 관리되는 서비스 클래스임을 나타낸다. 이 어노테이션을 통해 해당 클래스를 빈으로 등록하고 필요한 곳에 주입하여 사용할 수 있도록 한다.
이 클래스는 Controller에서 사용할 것이다.
@Autowired : 의존성 주입을 수행하는데 사용된다. PostRepository 인터페이스를 구현한 구현체를 주입받기 위해 사용되었다. 그리고 스프링은 @Autowired 어노테이션이 붙은 필드에 해당하는 객체를 찾아서 자동으로 주입해준다.
Controller
package com.example.CRUD.controller;
import com.example.CRUD.domain.Post;
import com.example.CRUD.dto.PostDTO;
import com.example.CRUD.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/posts")
public class PostController {
private PostService postService;
@Autowired
public PostController(PostService postService) {
this.postService = postService;
}
@GetMapping("/")
public List<Post> getAllPosts() {
return postService.getAllPosts();
}
@GetMapping("/get-post")
public Post getPostById(@RequestParam("id") Long id){
return postService.getPostById(id);
}
@PostMapping("/create-post")
public Post createPost(@RequestBody PostDTO postDTO){
return postService.createPost(postDTO);
}
@PutMapping("/update-post/{id}")
public Post updatePost(@PathVariable("id") Long id, @RequestBody PostDTO updatedPost) {
return postService.updatePost(id, updatedPost);
}
@DeleteMapping("/delete-post/{id}")
public void deletePost(@PathVariable("id") Long id) {
postService.deletePost(id);
}
}
위 클래스는 클라이언트 요청을 처리하는 컨트롤러 클래스이다. 서비스 클래스를 주입받고 있으며 서비스 클래스에서 구현된 메소드들을 사용하여 CRUD를 처리한다.
@RestController : 이 어노테이션은 RESTful 웹서비스의 엔드포인트로 사용됨을 나타낸다. HTTP 요청을 처리하고, JSON 또는 XML과 같은 데이터를 반환할 수 있다.
@RequestMapping : 이 어노테이션은 url 경로를 지정한다. "/posts" 경로에 해당하는 모든 요청이 이 컨트롤러에서 처리된다.
@Autowired : 마찬가지로 의존성 주입을 수행하며 PostService를 자동으로 찾아서 주입해준다.
파라미터 어노테이션
@RequestParm : 주로 GET 요청에서 쓰이며 URL에 직접 포함되는 파라미터로, 주로 검색 또는 필터링을 구현할 떄 사용된다. 'http://example.com/api/?id=1'
@RequestBody : 주로 POST, PUT과 같은 새로운 리소스를 생성하거나 데이터를 업데이트 할 떄 사용된다. 본문에 JSON 또는 XML과 같은 형식으로 데이터를 포함하여 사용한다.
@PathVariable : 주로 DELETE 요청에서 사용하며 URL 경로에 포함하여 사용된다. 'http://example.com/api/delete/1'
POSTMAN을 사용한 테스트
CREATE - 게시글 생성
title과 content를 JSON형태의 데이터로 요청하였다.
READ - 게시글 전체 조회하기, id로 조회하기
CREATE된 두개의 인스턴스를 찾을 수 있다.
아이디가 1인 인스턴스를 조회할 수 있다.
UPDATE - 게시글 수정
id가 1인 인스턴스의 제목과 내용을 수정하여 PUT 요청을 하였다.
확인하기
수정이 된 것을 확인할 수 있다.
DELETE
id가 1인 인스턴스를 DELETE 요청을 하였다.
확인하기
id가 2인 데이터만 조회된 것을 알 수 있다.
'Spring' 카테고리의 다른 글
Spring Boot - Lombok (0) | 2024.04.03 |
---|---|
Spring Security 살펴보기 (0) | 2024.02.22 |
스프링 핵심 원리 - 스프링 컨테이너 생성, 빈 조회 (0) | 2024.01.22 |
스프링 핵심 원리 - 예제 만들기 (0) | 2024.01.14 |
객체지향 설계와, 스프링 (1) | 2024.01.12 |