작업 환경
IDE: IntelliJ
Spring Boot: 3.2.3
Java: 20
Spring Boot에서 DB와 통신하기 위해서는 기본적으로 다음과 같은 단계가 있습니다.
1. Entity 클래스
Entity 클래스를 작성하여 MySQL 테이블과 매핑하게 됩니다. JPA(Java Persistence API) 어노테이션을 사용하여 RDBMS를 관리 (생성, 조회, 업데이트, 삭제) 할 수 있습니다.
2. Repository 인터페이스
Spring Data JPA에서 제공하는 강력한 기능 중 하나로, SQL을 직접 작성하지 않고도, 데이터베이스의 CRUD(Create, Read, Update, Delete) 작업을 수행 할 수 있는 매서드를 자동 생성합니다.
3. Service 클래스
Service 클래스에서 기본적인 로직을 구현합니다.
4. Controller 클래스
HTTP 요청이 오면 처리하고 응답을 반환하는 클래스입니다. API의 엔드포인트를 매핑하여 Service 클래스에서 구현한 로직을 처리 할 수 있습니다.
Entity 클래스는 MySQL "테이블과 매핑하는 기능을 수행한다" 말씀 드렸습니다. 다음 예시를 보겠습니다.
package com.example.weddingCard.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "user")
public class User {
@Id
@Column(name = "user_id")
private String userId;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
먼저 @Entity 어노테이션 입니다. 해당 클래스가 JPA Entity임을 나타내는데 사용합니다.
그리고 아래에 @Table 어노테이션을 볼 수 있습니다. 해당 어노테이션은 Entity가 매핑 될 때 테이블의 정보를 명시해줍니다. (name = "user") 을 명시함으로써, User 클래스가 user 테이블에 매핑됨을 나타냅니다.
하지만 클래스의 이름과 테이블의 이름이 동일한 경우에는 @Table 어노테이션을 생략하여도 정상적으로 매핑이 됩니다.
또한 JPA는 클래스 이름이 User와 같이 대문자로 시작하더라도 user 테이블과 알아서 매핑해줍니다.
그러면 @Table 어노테이션은 왜 존재하는걸까요? 아래의 예시를 살펴봅시다.
package com.example.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "new_user")
public class NewUser {
...
}
Java의 클래스 네이밍 규칙은 파스칼 케이스(pascal case)로 여러개의 단어가 조합되면 대문자로 구분을 합니다.
하지만 MySQL은 반대로 소문자 사용을 권장하고 여러개의 단어가 조합되면 언더스코어( _ )로 구분합니다.
때문에 네이밍을 하나에 통일하면 반대는 무조건 규칙을 어기게 됩니다.
이러한 이유로 @Table 어노테이션을 사용하여 이름을 명시하는 것이 바람직하고 권장되는 것입니다.
Entity를 테이블에 매핑 할 때 되도록이면 @Table 어노테이션을 사용하여 명시해줍시다❗
다음으로 @Id 어노테이션과 코드에는 작성되어 있지 않지만, @GeneratedValue 어노테이션에 대해 알아보겠습니다.
@Id 어노테이션은 JPA에서 Entity의 기본 키(Primary Key) 필드를 지정하는데 사용합니다. 쉽게 생각하여 기본 키(PK)를 매핑 해주면 됩니다.
그리고 @GeneratedValue 어노테이션은 @Id 어노테이션과 함께 사용되는데, 기본 키가 값을 어떻게 자동 생성할지 정의하는 데 사용됩니다. 때문에 타입이 INT, BIGINT 등 DB가 해당 타입의 자동 증가 기능을 지원 할 때 사용합니다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer user_id;
@GeneratedValue의 strategy 속성은 INDENTITY, SEQUENCE, TABLE, AUTO 4가지가 존재합니다. 일반적으로 사용하시는 SQL 모델에 따라 결정하시면 됩니다.
값 | SQL 모델 | 설명 |
---|---|---|
INDENTITY | MySQL 등 | Entity가 데이터베이스에 삽입 될 때, 자동으로 다음 값 생성 |
SEQUENCE | ORACLE 등 | Sequence를 사용하여 데이터베이스에서 순서대로 증가하는 값을 생성 |
TABLE | 모든 DBMS | Sequence를 지원하지 않는 모델을 위해 키 생성을 위한 Table을 생성 |
AUTO | 데이터 베이스에 맞게 자동 선택 |
참고로, 제가 작성한 코드는 user_id의 타입을 String으로 하였기 때문에 @GeneratedValue 어노테이션은 사용하지 않았습니다.
SQL의 CRUD(Create, Read, Update, Delete) 기능을 대신 수행하는 Repository 인터페이스에 대해 알아보겠습니다. 주요 기능은 다음과 같습니다.
데이터 접근 추상화 : Repository 인터페이스는 데이터베이스의 작업을 추상화합니다. 덕분에 개발자는 인터페이스를 통해 선언한 메서드만 사용하여 데이터베이스와 상호작용이 가능합니다.
도메인 중심 설계 지원 : Entity 클래스에 대한 데이터 액세스 메서드를 정의해주어 도메인 중심의 설계를 할 수 있게 됩니다.
손쉬운 CRUD 작업 : Spring Data JPA는 기본적인 CRUD(Create, Read, Update, Delete) 작업을 위한 메서드를 제공하는 'CrudRepository'와 'JpaRepository' 같은 내장 인터페이스를 제공합니다.
Repository 인터페이스의 정의는 간단합니다.
package com.example.repository;
import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
}
인터페이스를 생성하여 'CrudRepository', 'JpaRepository' 중 하나를 확장합니다. 그리고 타입 파라미터는 다음 두 가지를 기준으로 작성하시면 됩니다.
1. 첫 번째 타입 파라미터 : Repository가 관리할 Entity의 클래스 타입입니다. 즉 이 Repository가 어떤 Entity에 대한 데이터 액세스 작업을 수행 할 지 정해주시면 됩니다. 저는 위에 작성할 User Entity에 대한 데이터 액세스 작업을 수행 할 것이기 때문에 User을 작성하였습니다.
2. 두 번째 타입 파라미터 : 해당 Entity의 기본 키 필드의 타입입니다. Entity에서 @Id 어노테이션에 붙은 타입을 작성하시면 됩니다. 저는 Integer로 작성했습니다.
주요 기능에 대해 설명 드리겠습니다.
CrudRepoistory는 기본적인 데이터 액세스의 기능을 제공합니다.
save(S entity) : 주어진 Entity를 저장합니다. 이미 존재하는 경우 업데이트합니다.
findById(ID id) : 주어진 ID에 해당하는 Entity를 찾아 Optional로 반환합니다.
existsById(Id id) : 주어진 ID의 Entity가 존재하는지 확인합니다.
findAll() : 모든 Entity를 List로 반환합니다.
findAllById(Interable <ID> ids) : 주어진 ID 컬렉션에 해당하는 모든 Entity를 찾아 반환합니다.
count() : Entity의 총 개수를 반환합니다.
deleteById(Id id) : 주어진 ID에 해당하는 Entity를 삭제합니다.
delete(S entity) : 주어진 Entity를 삭제합니다.
deleteAll() : 모든 Entity를 삭제합니다.
때문에 시간과 노력이 크게 줄어드는 효과를 가져오지요.
또한 JpaRepository는 CrudRepository를 확장하고, JPA에 특화된 기능을 추가로 제공합니다. 때문에 JpaRepository는 CrudRepository의 모든 메서드가 포함되어 있습니다.
아래는 JpaRepository에 추가된 기능들 입니다.
findAll(Sort sort) : 주어진 정렬 조건에 따라 모든 Entity를 정렬하여 반환합니다.
findAll(Pageable pageable) : 페이징 처리된 결과를 반환합니다.
flush() : 변경 사항을 즉시 데이터베이스에 반영합니다.
saveAndFlush(S entity) : 주어진 Entity를 저장하고 즉시 데이터베이스에 반영합니다.
deleteAllInBatch() : 모든 Entity를 배치로 삭제합니다.
위 메서드를 통해 기본적인 CRUD 작업 외에도 정렬, 페이징, 배치 작업 등 보다 고급 데이터 액세스 기능을 수행 할 수 있습니다.
Service 클래스는 애프리케이션의 비즈니스 로직을 구현하는 데 사용됩니다. 예시로 userId 값을 사용하여 User을 생성하고 저장하는 로직을 보여드리겠습니다.
package com.example.weddingCard.service;
import com.example.weddingCard.entity.User;
import com.example.weddingCard.repository.UserRepository;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository){
this.userRepository = userRepository;
}
@Transactional
public User saveUserId(String userId) {
User user = new User();
user.setUserId(userId);
return userRepository.save(user);
}
}
먼저 @Serivce 어노테이션을 사용하여, 이 클래스가 Service 계층의 구성 요소임을 나타냅니다.
@Autowride 어노테이션은 Spring의 의존성 주입 기능을 활용해 생성자, 세터 메서드에 자동으로 의존성을 주입해줍니다.
@Transactional 어노테이션은 메서드나 클래스를 트랜잭션의 경계로 정의하여, 해당 메서드의 실행이 하나의 트랜잭션으로 처리되게 합니다.
트랜잭션으로 처리되면 작업이 성공적으로 완료되면 커밋하고, 실패하여도 롤백하여 작업을 자동으로 처리할 수 있으며, 안정성도 향상됩니다.
저는 간단하게 saveUserId 메소드를 작성하여 User Entity를 저장하는 로직을 작성하였습니다.
Controller 클래스는 클라이언트의 요청을 처리하고, 비즈니스 로직을 실행하는 중요한 클래스입니다.
보통 @Controller 어노테이션이나 @RestController 어노테이션을 사용합니다.
@Controller 어노테이션은 주로 HTML 페이지에 사용됩니다. @RestController 어노테이션은 주로 JSONdlsk XML 형식의 데이터를 반환하는 REST API를 구현할 때 유용하게 사용됩니다.
package com.example.weddingCard.controller;
import com.example.weddingCard.entity.User;
import com.example.weddingCard.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
@PostMapping("/create")
public User createUserId(@RequestBody User user){
String userId = user.getUserId();
return userService.saveUserId(userId);
}
}
Service 클래스에서 만든 로직을 사용하는 Controller를 간단하게 작성하였습니다.
@PostMapping 어노테이션으로 /create 경로를 매핑했습니다.
@PostMapping 뿐만 아니라 @GetMapping, @DeleteMapping 등 다양한 어노테이션이 존재하며, 기본적으로 사용 원리는 동일합니다.
위 Controller 클래스 덕분에 클라이언트가 서버로 POST /create 요청을 보내면, 서버는 JSON Body에 있는 userId를 확인하고 String 문자열을 user 테이블 내의 user_id에 저장하게 됩니다.
그럼 해당 기능이 잘 작동하는지 Postman을 사용하여 확인하도록 하겠습니다.
Postman에서 주소에 localhost:8080/create로 작성해주고, JSON Body에 "test001user" String 문자열을 넣어주었습니다. 응답은 200 OK로 성공적으로 전송되었네요.
데이터베이스에서 확인해본 결과 정상으로 test001user가 user 테이블에 들어왔습니다.