Ver2.0 ๐ Spring MVC๋ก RESTful API ๊ฐ๋ฐํ๊ธฐ: ์ด๋ณด์๋ ์ฝ๊ฒ ๋ฐ๋ผํ ์ ์๋ ์๋ฒฝ ๊ฐ์ด๋ ๐

๐ Spring MVC๋ก RESTful API ๊ฐ๋ฐํ๊ธฐ: ์ด๋ณด์๋ ์ฝ๊ฒ ๋ฐ๋ผํ ์ ์๋ ์๋ฒฝ ๊ฐ์ด๋ ๐
์๋ ํ์ธ์, ๊ฐ๋ฐ์ ์ฌ๋ฌ๋ถ! ์ค๋์ Spring MVC๋ฅผ ํ์ฉํด์ RESTful API๋ฅผ ๊ฐ๋ฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด ํจ๊ป ์์๋ณผ๊ฒ์. ๐ Java ๊ฐ๋ฐ์๋ผ๋ฉด ํ ๋ฒ์ฏค์ Spring Framework๋ฅผ ์ ํด๋ดค์ ํ ๋ฐ, ์ด๋ฒ ๊ธ์์๋ ์ด๋ณด์๋ ์ฝ๊ฒ ๋ฐ๋ผํ ์ ์๋๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ์ค๋ช ํด๋๋ฆด๊ฒ์!
์์ฆ ํ๋ก ํธ์๋์ ๋ฐฑ์๋๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฐ๋ฐ ๋ฐฉ์์ด ๋์ธ์ธ๋ฐ, ์ด๋ฐ ํธ๋ ๋์ ๋ง์ถฐ RESTful API ๊ฐ๋ฐ ์คํฌ์ ํ์๊ฐ ๋์์ด์. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ API ๊ฐ๋ฐ ๊ด๋ จ ์ฌ๋ฅ ๊ฑฐ๋๊ฐ ํ๋ฐํ๊ฒ ์ด๋ฃจ์ด์ง๊ณ ์์ฃ ! ๊ทธ๋งํผ ์์๊ฐ ๋ง๋ค๋ ๋ป์ด๋๊น, ์ด ๊ธฐํ์ ํ์คํ๊ฒ ๋ฐฐ์๋ด์~ ใ ใ
๐ ๋ชฉ์ฐจ
- Spring MVC์ RESTful API ๊ฐ๋ ์ดํดํ๊ธฐ
- ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ ํ๊ธฐ
- ์ฒซ ๋ฒ์งธ RESTful API ๋ง๋ค๊ธฐ
- HTTP ๋ฉ์๋ ํ์ฉํ๊ธฐ
- ์์ฒญ/์๋ต ์ฒ๋ฆฌํ๊ธฐ
- ์์ธ ์ฒ๋ฆฌ์ ์ ํจ์ฑ ๊ฒ์ฌ
- API ๋ฌธ์ํํ๊ธฐ
- ๋ณด์ ์ ์ฉํ๊ธฐ
- ์ค์ ํ๋ก์ ํธ: ๊ฐ๋จํ API ์๋น์ค ๊ตฌํ
- ๋ง๋ฌด๋ฆฌ ๋ฐ ๋ค์ ๋จ๊ณ
1. Spring MVC์ RESTful API ๊ฐ๋ ์ดํดํ๊ธฐ ๐ง
์ผ๋จ ๊ธฐ๋ณธ ๊ฐ๋ ๋ถํฐ ํ์คํ ์ก๊ณ ๊ฐ์๊ตฌ์! ๋๋ฌด ์ด๋ ต๊ฒ ์๊ฐํ์ง ๋ง์ธ์~ ์ฝ๊ฒ ์ค๋ช ํด๋๋ฆด๊ฒ์ ใ ใ
Spring MVC๋ ๋ญ๊น์? ๐ค
Spring MVC๋ Model-View-Controller ํจํด์ ๊ธฐ๋ฐ์ผ๋ก ํ ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ํ๋ ์์ํฌ์์. ๊ฐ๋จํ ๋งํ๋ฉด:
- Model: ๋ฐ์ดํฐ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋นํด์
- View: ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง๋ ํ๋ฉด์ ๋ด๋นํด์
- Controller: ์ฌ์ฉ์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ Model๊ณผ View๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ ์ญํ ์ ํด์
RESTful API๋ ๋ญ๊ฐ์? ๐
REST(Representational State Transfer)๋ ์น ์๋น์ค๋ฅผ ์ค๊ณํ๋ ์ํคํ ์ฒ ์คํ์ผ์ด์์. RESTful API๋ ์ด ์์น์ ๋ฐ๋ฅด๋ API๋ฅผ ๋งํ์ฃ .
์ฝ๊ฒ ๋งํ๋ฉด, ์ธํฐ๋ท ์ฃผ์(URL)๋ก ์์์ ํํํ๊ณ , HTTP ๋ฉ์๋(GET, POST, PUT, DELETE ๋ฑ)๋ก ์์์ ๋ํ ํ๋์ ์ ์ํ๋ ๋ฐฉ์์ด์์. ๋ง์น "์ด ์ฃผ์๋ก ์์ ์ด๋ฐ ํ๋์ ํด์ค~"๋ผ๊ณ ๋งํ๋ ๊ฒ๊ณผ ๊ฐ์์! ใ ใ ใ
์ RESTful API๊ฐ ์ค์ํ ๊น์? ๐
์์ฆ ๊ฐ๋ฐ ํธ๋ ๋๋ ํ๋ก ํธ์๋์ ๋ฐฑ์๋๋ฅผ ๋ถ๋ฆฌํ๋ ๋ฐฉ์์ผ๋ก ๊ฐ๊ณ ์์ด์. ํ๋ก ํธ์๋(React, Vue, Angular ๋ฑ)์ ๋ฐฑ์๋(Spring, Node.js ๋ฑ) ์ฌ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ๋ฒ์ผ๋ก RESTful API๊ฐ ๋๋ฆฌ ์ฌ์ฉ๋๊ณ ์์ฃ .
๋ํ ๋ชจ๋ฐ์ผ ์ฑ, ์น, IoT ๊ธฐ๊ธฐ ๋ฑ ๋ค์ํ ํด๋ผ์ด์ธํธ๋ฅผ ์ง์ํด์ผ ํ๋ ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ RESTful API๊ฐ ํ์์ ์ด์์. ์ฌ๋ฅ๋ท์์๋ ์ด๋ฐ API ๊ฐ๋ฐ ๊ด๋ จ ์ฌ๋ฅ์ด ์ธ๊ธฐ๊ฐ ๋ง๋ค๊ณ ํ๋ค์! ๐
2. ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ ํ๊ธฐ โ๏ธ
์, ์ด์ ์ค์ ๋ก ์ฝ๋ฉ์ ์์ํด๋ณผ๊น์? ๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ๋ถํฐ ์ธํ ํด๋ด์!
ํ์ํ ๋๊ตฌ๋ค ๐ ๏ธ
- JDK 8 ์ด์ (11 ๋๋ 17 ์ถ์ฒ)
- IDE (IntelliJ IDEA, Eclipse, VS Code ๋ฑ)
- Maven ๋๋ Gradle (์์กด์ฑ ๊ด๋ฆฌ ๋๊ตฌ)
- Postman (API ํ ์คํธ ๋๊ตฌ)
Spring Boot ํ๋ก์ ํธ ์์ฑํ๊ธฐ ๐
Spring Boot๋ฅผ ์ฌ์ฉํ๋ฉด Spring MVC ํ๋ก์ ํธ๋ฅผ ์ฝ๊ฒ ์ค์ ํ ์ ์์ด์. Spring Initializr(https://start.spring.io/)๋ฅผ ์ฌ์ฉํด์ ํ๋ก์ ํธ๋ฅผ ์์ฑํด๋ณผ๊ฒ์!
Spring Initializr์์ ๋ค์ ์ค์ ์ผ๋ก ํ๋ก์ ํธ๋ฅผ ์์ฑํด์ฃผ์ธ์:
- Project: Maven Project
- Language: Java
- Spring Boot: 2.7.x ๋๋ ์ต์ ๋ฒ์
- Group: com.example
- Artifact: rest-api-demo
- Dependencies: Spring Web, Spring Data JPA, H2 Database, Lombok
'Generate' ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํ๋ก์ ํธ zip ํ์ผ์ด ๋ค์ด๋ก๋๋ผ์. ์ด๊ฑธ ์์ถ ํด์ ํ๊ณ IDE๋ก ์ด์ด์ฃผ์ธ์!
๊ธฐ๋ณธ ์์กด์ฑ ์ดํด๋ณด๊ธฐ ๐ฆ
์์ฑ๋ pom.xml ํ์ผ์ ์ดํด๋ณผ๊น์? ์ฃผ์ ์์กด์ฑ๋ค์ด ์ด๋ฏธ ์ถ๊ฐ๋์ด ์์ด์:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
๊ฐ ์์กด์ฑ์ ์ญํ ์ ๊ฐ๋จํ ์ค๋ช ํ์๋ฉด:
- spring-boot-starter-web: Spring MVC, RESTful API ๊ฐ๋ฐ์ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- spring-boot-starter-data-jpa: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ์ ์ํ JPA ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- h2: ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค, ๊ฐ๋ฐ ๋ฐ ํ ์คํธ์ฉ
- lombok: ๋ฐ๋ณต์ ์ธ ์ฝ๋(getter, setter ๋ฑ)๋ฅผ ์ค์ฌ์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
์ ํ๋ฆฌ์ผ์ด์ ์ค์ ํ๊ธฐ โ๏ธ
src/main/resources/application.properties ํ์ผ์ ๋ค์ ์ค์ ์ ์ถ๊ฐํด์ฃผ์ธ์:
# ์๋ฒ ํฌํธ ์ค์
server.port=8080
# H2 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# H2 ์ฝ์ ํ์ฑํ
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA ์ค์
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
์ด์ ๊ธฐ๋ณธ ํ๊ฒฝ ์ค์ ์ด ์๋ฃ๋์ด์! ํ๋ก์ ํธ๋ฅผ ์คํํด๋ณด๋ฉด Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ด 8080 ํฌํธ์์ ์คํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ด์. ์ง์ง์ง! ๐
3. ์ฒซ ๋ฒ์งธ RESTful API ๋ง๋ค๊ธฐ ๐ฏ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก API๋ฅผ ๋ง๋ค์ด๋ณผ๊น์? ๊ฐ๋จํ ์ฌ์ฉ์(User) API๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์!
ํ๋ก์ ํธ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ ๐
๋จผ์ ํจํค์ง ๊ตฌ์กฐ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ง๋ค์ด์ฃผ์ธ์:
src/main/java/com/example/restapidemmo/
โโโ controller/
โโโ model/
โโโ repository/
โโโ service/
โโโ RestApiDemoApplication.java
๋ชจ๋ธ(Entity) ๋ง๋ค๊ธฐ ๐
User ์ํฐํฐ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์. model ํจํค์ง์ User.java ํ์ผ์ ์์ฑํด์ฃผ์ธ์:
package com.example.restapidemo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;
}
์ฌ๊ธฐ์ Lombok์ @Data ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด getter, setter, toString, equals, hashCode ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ์์ฑํด์ค์. ์ง์ง ํธํด์ ใ ใ ใ
๋ฆฌํฌ์งํ ๋ฆฌ ๋ง๋ค๊ธฐ ๐๏ธ
์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ์ ์ํ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์. repository ํจํค์ง์ UserRepository.java ํ์ผ์ ์์ฑํด์ฃผ์ธ์:
package com.example.restapidemo.repository;
import com.example.restapidemo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// JpaRepository๊ฐ ๊ธฐ๋ณธ์ ์ธ CRUD ๋ฉ์๋๋ฅผ ์ ๊ณตํด์ค๋๋ค!
}
Spring Data JPA์ JpaRepository๋ฅผ ์์๋ฐ์ผ๋ฉด ๊ธฐ๋ณธ์ ์ธ CRUD(Create, Read, Update, Delete) ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ์ฌ์ฉํ ์ ์์ด์. ์ง์ง ๊ฐ๋ฐ์ ์ธ์ ๊ฟ์ด์ฃ ? ๐
์๋น์ค ๋ง๋ค๊ธฐ โ๏ธ
๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋นํ ์๋น์ค ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์. service ํจํค์ง์ UserService.java ํ์ผ์ ์์ฑํด์ฃผ์ธ์:
package com.example.restapidemo.service;
import com.example.restapidemo.model.User;
import com.example.restapidemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public User createUser(User user) {
return userRepository.save(user);
}
public Optional<User> updateUser(Long id, User userDetails) {
return userRepository.findById(id)
.map(user -> {
user.setName(userDetails.getName());
user.setEmail(userDetails.getEmail());
user.setAge(userDetails.getAge());
return userRepository.save(user);
});
}
public boolean deleteUser(Long id) {
return userRepository.findById(id)
.map(user -> {
userRepository.delete(user);
return true;
})
.orElse(false);
}
}
์๋น์ค ํด๋์ค์์๋ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ๊ณ , ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํด์. @Autowired๋ฅผ ์ฌ์ฉํด์ ์์กด์ฑ ์ฃผ์ (DI)์ ๋ฐ๊ณ ์๋ค์!
์ปจํธ๋กค๋ฌ ๋ง๋ค๊ธฐ ๐ฎ
์ด์ RESTful API์ ํต์ฌ์ธ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์. controller ํจํค์ง์ UserController.java ํ์ผ์ ์์ฑํด์ฃผ์ธ์:
package com.example.restapidemo.controller;
import com.example.restapidemo.model.User;
import com.example.restapidemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
// ๋ชจ๋ ์ฌ์ฉ์ ์กฐํ
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return new ResponseEntity<>(users, HttpStatus.OK);
}
// ํน์ ์ฌ์ฉ์ ์กฐํ
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.map(user -> new ResponseEntity<>(user, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
// ์ฌ์ฉ์ ์์ฑ
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
// ์ฌ์ฉ์ ์์
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.updateUser(id, user)
.map(updatedUser -> new ResponseEntity<>(updatedUser, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
// ์ฌ์ฉ์ ์ญ์
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
boolean deleted = userService.deleteUser(id);
return deleted ?
new ResponseEntity<>(HttpStatus.NO_CONTENT) :
new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
์ปจํธ๋กค๋ฌ์์๋ ๋ค์๊ณผ ๊ฐ์ RESTful API ์๋ํฌ์ธํธ๋ฅผ ์ ์ํ์ด์:
- GET /api/users: ๋ชจ๋ ์ฌ์ฉ์ ์กฐํ
- GET /api/users/{id}: ํน์ ์ฌ์ฉ์ ์กฐํ
- POST /api/users: ์ ์ฌ์ฉ์ ์์ฑ
- PUT /api/users/{id}: ์ฌ์ฉ์ ์ ๋ณด ์์
- DELETE /api/users/{id}: ์ฌ์ฉ์ ์ญ์
์ฌ๊ธฐ์ @RestController๋ @Controller + @ResponseBody๋ฅผ ํฉ์น ๊ฒ์ผ๋ก, ๋ฉ์๋์ ๋ฐํ๊ฐ์ด HTTP ์๋ต ๋ณธ๋ฌธ์ ์ง์ ๋งคํ๋๋ค๋ ์๋ฏธ์์. REST API๋ฅผ ๋ง๋ค ๋ ๋ฑ์ด์ฃ ! ๐
์ ํ๋ฆฌ์ผ์ด์ ์คํํ๊ธฐ ๐
์ด์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํด๋ณผ๊น์? IDE์์ RestApiDemoApplication.java ํ์ผ์ ์คํํ๊ฑฐ๋, ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํด์ฃผ์ธ์:
./mvnw spring-boot:run
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์คํ๋๋ฉด http://localhost:8080/api/users ์ฃผ์๋ก API๋ฅผ ํ ์คํธํด๋ณผ ์ ์์ด์!
4. HTTP ๋ฉ์๋ ํ์ฉํ๊ธฐ ๐
RESTful API์์๋ HTTP ๋ฉ์๋๋ฅผ ํ์ฉํด ์์์ ๋ํ ํ๋์ ์ ์ํด์. ๊ฐ ๋ฉ์๋์ ์๋ฏธ์ ์ฌ์ฉ๋ฒ์ ์์ธํ ์์๋ณผ๊ฒ์!
์ฃผ์ HTTP ๋ฉ์๋ ๐
- GET: ์์ ์กฐํ (Read)
- POST: ์์ ์์ฑ (Create)
- PUT: ์์ ์ ์ฒด ์์ (Update)
- PATCH: ์์ ๋ถ๋ถ ์์ (Partial Update)
- DELETE: ์์ ์ญ์ (Delete)
GET ๋ฉ์๋ ํ์ฉํ๊ธฐ ๐
GET ๋ฉ์๋๋ ์์์ ์กฐํํ ๋ ์ฌ์ฉํด์. ์ฐ๋ฆฌ API์์๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ์ฃ :
// ๋ชจ๋ ์ฌ์ฉ์ ์กฐํ
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return new ResponseEntity<>(users, HttpStatus.OK);
}
// ํน์ ์ฌ์ฉ์ ์กฐํ
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.map(user -> new ResponseEntity<>(user, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
GET ์์ฒญ์ URL ํ๋ผ๋ฏธํฐ๋ ์ฟผ๋ฆฌ ์คํธ๋ง์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํด์. ์๋ฅผ ๋ค๋ฉด:
- URL ํ๋ผ๋ฏธํฐ:
/api/users/1(ID๊ฐ 1์ธ ์ฌ์ฉ์ ์กฐํ) - ์ฟผ๋ฆฌ ์คํธ๋ง:
/api/users?name=John(์ด๋ฆ์ด John์ธ ์ฌ์ฉ์ ์กฐํ)
POST ๋ฉ์๋ ํ์ฉํ๊ธฐ โ
POST ๋ฉ์๋๋ ์๋ก์ด ์์์ ์์ฑํ ๋ ์ฌ์ฉํด์:
// ์ฌ์ฉ์ ์์ฑ
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
POST ์์ฒญ์ ์์ฒญ ๋ณธ๋ฌธ(Request Body)์ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ์ ์กํด์. Spring MVC์์๋ @RequestBody ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํด ์์ฒญ ๋ณธ๋ฌธ์ ์๋ฐ ๊ฐ์ฒด๋ก ๋ณํํด์ค์.
POST ์์ฒญ ์์ (JSON ํ์):
{
"name": "ํ๊ธธ๋",
"email": "hong@example.com",
"age": 25
}
PUT ๋ฉ์๋ ํ์ฉํ๊ธฐ ๐
PUT ๋ฉ์๋๋ ์์์ ์์ ํ ๋์ฒด(์์ )ํ ๋ ์ฌ์ฉํด์:
// ์ฌ์ฉ์ ์์
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.updateUser(id, user)
.map(updatedUser -> new ResponseEntity<>(updatedUser, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
PUT ์์ฒญ๋ POST์ ๋ง์ฐฌ๊ฐ์ง๋ก ์์ฒญ ๋ณธ๋ฌธ์ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ์ ์กํด์. ๋ค๋ง, PUT์ ์์์ ์์ ํ ๋์ฒดํ๋ ์๋ฏธ์ด๋ฏ๋ก, ์์ฒญ ๋ณธ๋ฌธ์ ๋ชจ๋ ํ๋๋ฅผ ํฌํจํด์ผ ํด์.
PATCH ๋ฉ์๋ ํ์ฉํ๊ธฐ ๐ฉน
PATCH ๋ฉ์๋๋ ์์์ ๋ถ๋ถ์ ์ผ๋ก ์์ ํ ๋ ์ฌ์ฉํด์. ์ฐ๋ฆฌ ์์ ์๋ ์์ง๋ง, ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ ์ ์์ด์:
// ์ฌ์ฉ์ ๋ถ๋ถ ์์
@PatchMapping("/{id}")
public ResponseEntity<User> partialUpdateUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.partialUpdateUser(id, updates)
.map(updatedUser -> new ResponseEntity<>(updatedUser, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
PATCH ์์ฒญ์ ๋ณ๊ฒฝํ ํ๋๋ง ํฌํจํ๋ ์์ฒญ ๋ณธ๋ฌธ์ ์ ์กํด์. ์๋ฅผ ๋ค์ด, ์ด๋ฉ์ผ๋ง ๋ณ๊ฒฝํ๊ณ ์ถ๋ค๋ฉด:
{
"email": "new.email@example.com"
}
DELETE ๋ฉ์๋ ํ์ฉํ๊ธฐ โ
DELETE ๋ฉ์๋๋ ์์์ ์ญ์ ํ ๋ ์ฌ์ฉํด์:
// ์ฌ์ฉ์ ์ญ์
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
boolean deleted = userService.deleteUser(id);
return deleted ?
new ResponseEntity<>(HttpStatus.NO_CONTENT) :
new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
DELETE ์์ฒญ์ ๋ณดํต URL ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ์ญ์ ํ ์์์ ID๋ฅผ ์ ๋ฌํด์. ์์ฒญ ๋ณธ๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก ๋น์ด์์ด์.
์ด๋ ๊ฒ HTTP ๋ฉ์๋๋ฅผ ์ ์ ํ ํ์ฉํ๋ฉด RESTful API์ ์๋๋ฅผ ๋ช ํํ๊ฒ ํํํ ์ ์์ด์. ๊ฐ๋ฐ์๋ค์ด API๋ฅผ ๋ณด๊ณ ์ง๊ด์ ์ผ๋ก ์ดํดํ ์ ์๊ฒ ๋์ฃ ! ๐
5. ์์ฒญ/์๋ต ์ฒ๋ฆฌํ๊ธฐ ๐ค๐ฅ
RESTful API์์๋ ์์ฒญ๊ณผ ์๋ต์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ์์ธํ ์์๋ณผ๊ฒ์!
์์ฒญ ์ฒ๋ฆฌํ๊ธฐ ๐ฅ
Spring MVC์์๋ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ํด๋ผ์ด์ธํธ์ ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์์ด์:
1. ๊ฒฝ๋ก ๋ณ์ (@PathVariable) ๐
URL ๊ฒฝ๋ก์ ํฌํจ๋ ๋ณ์๋ฅผ ๋ฐ์ ๋ ์ฌ์ฉํด์:
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
// id ๊ฐ์ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌ
}
2. ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ (@RequestParam) ๐
URL์ ์ฟผ๋ฆฌ ์คํธ๋ง ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์ ๋ ์ฌ์ฉํด์:
@GetMapping("/users")
public ResponseEntity<List<User>> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer minAge) {
// name, minAge ๊ฐ์ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌ
}
3. ์์ฒญ ๋ณธ๋ฌธ (@RequestBody) ๐ฆ
JSON์ด๋ XML ๊ฐ์ ํ์์ ์์ฒญ ๋ณธ๋ฌธ์ ์๋ฐ ๊ฐ์ฒด๋ก ๋ณํํ ๋ ์ฌ์ฉํด์:
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// user ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌ
}
4. ์์ฒญ ํค๋ (@RequestHeader) ๐
HTTP ์์ฒญ ํค๋ ๊ฐ์ ๋ฐ์ ๋ ์ฌ์ฉํด์:
@GetMapping("/users")
public ResponseEntity<List<User>> getUsers(
@RequestHeader("Authorization") String authToken) {
// authToken ๊ฐ์ ์ฌ์ฉํ์ฌ ์ธ์ฆ ์ฒ๋ฆฌ
}
์๋ต ์ฒ๋ฆฌํ๊ธฐ ๐ค
Spring MVC์์๋ ResponseEntity ํด๋์ค๋ฅผ ์ฌ์ฉํด HTTP ์๋ต์ ๊ตฌ์ฑํ ์ ์์ด์:
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.map(user -> new ResponseEntity<>(user, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
ResponseEntity๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์์๋ฅผ ์ค์ ํ ์ ์์ด์:
- ์๋ต ๋ณธ๋ฌธ(Body): ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํํ ๋ฐ์ดํฐ
- ์ํ ์ฝ๋(Status Code): HTTP ์ํ ์ฝ๋ (200 OK, 404 Not Found ๋ฑ)
- ํค๋(Headers): HTTP ์๋ต ํค๋
์ฃผ์ HTTP ์ํ ์ฝ๋ ๐ฆ
RESTful API์์ ์์ฃผ ์ฌ์ฉ๋๋ HTTP ์ํ ์ฝ๋๋ฅผ ์์๋ณผ๊ฒ์:
- 200 OK: ์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋จ
- 201 Created: ์์์ด ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋จ
- 204 No Content: ์์ฒญ์ ์ฑ๊ณตํ์ง๋ง ๋ฐํํ ์ฝํ ์ธ ๊ฐ ์์
- 400 Bad Request: ์๋ชป๋ ์์ฒญ
- 401 Unauthorized: ์ธ์ฆ ์คํจ
- 403 Forbidden: ๊ถํ ๋ถ์กฑ
- 404 Not Found: ์์ฒญํ ์์์ ์ฐพ์ ์ ์์
- 500 Internal Server Error: ์๋ฒ ๋ด๋ถ ์ค๋ฅ
์๋ต ํ์ ๐
RESTful API์์๋ ์ฃผ๋ก JSON ํ์์ผ๋ก ์๋ต์ ๋ฐํํด์. Spring MVC์์๋ Jackson ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ์๋ฐ ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก JSON์ผ๋ก ๋ณํํด์ค์.
์ผ๊ด๋ ์๋ต ํ์์ ์ํด ๊ณตํต ์๋ต ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์์:
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
// ์์ฑ์, getter, setter ์๋ต
}
์ด ์๋ต ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ผ๊ด๋ ํ์์ ์๋ต์ ๋ฐํํ ์ ์์ด์:
@GetMapping("/users/{id}")
public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.map(user -> {
ApiResponse<User> response = new ApiResponse<>(true, "์ฌ์ฉ์๋ฅผ ์ฐพ์์ต๋๋ค.", user);
return new ResponseEntity<>(response, HttpStatus.OK);
})
.orElse(new ResponseEntity<>(
new ApiResponse<>(false, "์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.", null),
HttpStatus.NOT_FOUND
));
}
์ด๋ ๊ฒ ํ๋ฉด ํด๋ผ์ด์ธํธ๋ ํญ์ ๋์ผํ ํ์์ ์๋ต์ ๋ฐ์ ์ ์์ด์ ์ฒ๋ฆฌํ๊ธฐ๊ฐ ํธํด์ ธ์! ๐
6. ์์ธ ์ฒ๋ฆฌ์ ์ ํจ์ฑ ๊ฒ์ฌ ๐ก๏ธ
RESTful API์์ ์์ธ ์ฒ๋ฆฌ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋งค์ฐ ์ค์ํด์. ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณผ๊ฒ์!
์ ์ญ ์์ธ ์ฒ๋ฆฌ ๐
Spring MVC์์๋ @ControllerAdvice์ @ExceptionHandler๋ฅผ ์ฌ์ฉํด ์ ์ญ ์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์์ด์:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(ResourceNotFoundException ex) {
ApiResponse<Void> response = new ApiResponse<>(false, ex.getMessage(), null);
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception ex) {
ApiResponse<Void> response = new ApiResponse<>(false, "์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.", null);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ ์ ์ ์์ธ ํด๋์ค๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ ์ ์์ด์:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
์ด์ ์๋น์ค ํด๋์ค์์ ์ด ์์ธ๋ฅผ ๋์ง๋ฉด ๋ฉ๋๋ค:
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("ID๊ฐ " + id + "์ธ ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
}
์ ํจ์ฑ ๊ฒ์ฌ โ
Spring Boot์์๋ Bean Validation์ ์ฌ์ฉํด ๊ฐํธํ๊ฒ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ ์ ์์ด์. ๋จผ์ ์์กด์ฑ์ ์ถ๊ฐํด์ฃผ์ธ์:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
๊ทธ๋ฆฌ๊ณ ๋ชจ๋ธ ํด๋์ค์ ์ ํจ์ฑ ๊ฒ์ฌ ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํด์:
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "์ด๋ฆ์ ํ์ ํญ๋ชฉ์
๋๋ค.")
private String name;
@NotBlank(message = "์ด๋ฉ์ผ์ ํ์ ํญ๋ชฉ์
๋๋ค.")
@Email(message = "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
private String email;
@Min(value = 0, message = "๋์ด๋ 0 ์ด์์ด์ด์ผ ํฉ๋๋ค.")
@Max(value = 150, message = "๋์ด๋ 150 ์ดํ์ฌ์ผ ํฉ๋๋ค.")
private int age;
}
์ปจํธ๋กค๋ฌ์์๋ @Valid ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํด ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ์ฑํํด์:
@PostMapping
public ResponseEntity<ApiResponse<User>> createUser(@Valid @RequestBody User user) {
User createdUser = userService.createUser(user);
ApiResponse<User> response = new ApiResponse<>(true, "์ฌ์ฉ์๊ฐ ์์ฑ๋์์ต๋๋ค.", createdUser);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
์ ํจ์ฑ ๊ฒ์ฌ ์คํจ ์ ๋ฐ์ํ๋ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์์ธ ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํด์:
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
ApiResponse<Map<String, String>> response = new ApiResponse<>(false, "์ ํจ์ฑ ๊ฒ์ฌ ์คํจ", errors);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
์ด๋ ๊ฒ ํ๋ฉด ์ ํจ์ฑ ๊ฒ์ฌ ์คํจ ์ ์ด๋ค ํ๋์์ ์ด๋ค ๋ฌธ์ ๊ฐ ์๋์ง ์์ธํ ์ ๋ณด๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๊ณตํ ์ ์์ด์! ๐
7. API ๋ฌธ์ํํ๊ธฐ ๐
API๋ฅผ ๊ฐ๋ฐํ๋ค๋ฉด ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค์ด ์ฝ๊ฒ ์ดํดํ๊ณ ์ฌ์ฉํ ์ ์๋๋ก ๋ฌธ์ํํ๋ ๊ฒ์ด ์ค์ํด์. Spring Boot์์๋ Swagger๋ SpringDoc์ ์ฌ์ฉํด API ๋ฌธ์๋ฅผ ์๋์ผ๋ก ์์ฑํ ์ ์์ด์!
SpringDoc OpenAPI ์ฌ์ฉํ๊ธฐ ๐
SpringDoc์ OpenAPI 3.0 ์คํ์ ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์. ๋จผ์ ์์กด์ฑ์ ์ถ๊ฐํด์ฃผ์ธ์:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
์ด์ OpenAPI ์ค์ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์:
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("์ฌ์ฉ์ ๊ด๋ฆฌ API")
.description("Spring Boot๋ก ๊ฐ๋ฐํ RESTful API ์์ ")
.version("1.0.0")
.contact(new Contact()
.name("๊ฐ๋ฐ์ ์ด๋ฆ")
.email("developer@example.com"))
.license(new License().name("MIT License")));
}
}
์ปจํธ๋กค๋ฌ์ ๋ชจ๋ธ ํด๋์ค์ API ๋ฌธ์ํ๋ฅผ ์ํ ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํด์:
@RestController
@RequestMapping("/api/users")
@Tag(name = "์ฌ์ฉ์ ๊ด๋ฆฌ", description = "์ฌ์ฉ์ CRUD API")
public class UserController {
// ...
@Operation(summary = "์ฌ์ฉ์ ์กฐํ", description = "ID๋ก ์ฌ์ฉ์๋ฅผ ์กฐํํฉ๋๋ค.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "์ฌ์ฉ์ ์กฐํ ์ฑ๊ณต"),
@ApiResponse(responseCode = "404", description = "์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์")
})
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
// ...
}
// ๋ค๋ฅธ ๋ฉ์๋์๋ ์ ์ฌํ๊ฒ ์ด๋
ธํ
์ด์
์ถ๊ฐ
}
์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๊ณ http://localhost:8080/swagger-ui.html์ ์ ์ํ๋ฉด ์๋์ผ๋ก ์์ฑ๋ API ๋ฌธ์๋ฅผ ๋ณผ ์ ์์ด์!
์ด๋ ๊ฒ API ๋ฌธ์๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ด ์์ด์:
- API ์๋ํฌ์ธํธ, ํ๋ผ๋ฏธํฐ, ์๋ต ํ์ ๋ฑ์ ํ๋์ ํ์ธํ ์ ์์ด์
- API๋ฅผ ์ง์ ํ ์คํธํด๋ณผ ์ ์๋ UI๋ฅผ ์ ๊ณตํด์
- ์ฝ๋๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ฌธ์๋ ์๋์ผ๋ก ์ ๋ฐ์ดํธ๋ผ์
- ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค์ด API๋ฅผ ์ฝ๊ฒ ์ดํดํ๊ณ ์ฌ์ฉํ ์ ์์ด์
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ API ๊ฐ๋ฐ ๊ด๋ จ ์ฌ๋ฅ์ ํ๋งคํ๋ค๋ฉด, ์ด๋ ๊ฒ ๋ฌธ์ํ๊ฐ ์ ๋ API๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ด ํฐ ๊ฒฝ์๋ ฅ์ด ๋ ์ ์์ด์! ๐
8. ๋ณด์ ์ ์ฉํ๊ธฐ ๐
RESTful API์ ๋ณด์์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ณผ๊ฒ์. Spring Security๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ๋ณด์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ด์!
Spring Security ์ค์ ํ๊ธฐ ๐ก๏ธ
๋จผ์ Spring Security ์์กด์ฑ์ ์ถ๊ฐํด์ฃผ์ธ์:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
๊ธฐ๋ณธ์ ์ธ ๋ณด์ ์ค์ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // RESTful API์์๋ CSRF ๋ณดํธ๊ฐ ํ์ ์์ ์ ์์ด์
.authorizeRequests()
.antMatchers("/api/public/**").permitAll() // ๊ณต๊ฐ API
.antMatchers("/api/users/**").authenticated() // ์ธ์ฆ ํ์
.anyRequest().authenticated()
.and()
.httpBasic(); // ๊ธฐ๋ณธ ์ธ์ฆ ์ฌ์ฉ
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("password")).roles("USER")
.and()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
์ด ์ค์ ์ ๋งค์ฐ ๊ธฐ๋ณธ์ ์ธ ๊ฒ์ด๊ณ , ์ค์ ํ๋ก์ ํธ์์๋ ๋ ๋ณต์กํ ๋ณด์ ์๊ตฌ์ฌํญ์ด ์์ ์ ์์ด์. ํ์ง๋ง ์ด ์์ ๋ก Spring Security์ ๊ธฐ๋ณธ ๊ฐ๋ ์ ์ดํดํ ์ ์์ด์!
JWT ์ธ์ฆ ๊ตฌํํ๊ธฐ ๐
RESTful API์์๋ JWT(JSON Web Token)๋ฅผ ์ฌ์ฉํ ์ธ์ฆ ๋ฐฉ์์ด ๋ง์ด ์ฌ์ฉ๋ผ์. JWT๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ถ๊ฐ ์์กด์ฑ์ด ํ์ํด์:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JWT ํ ํฐ์ ์์ฑํ๊ณ ๊ฒ์ฆํ๋ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์:
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
๊ทธ๋ฆฌ๊ณ JWT ํํฐ๋ฅผ ๋ง๋ค์ด ์ธ์ฆ ๊ณผ์ ์ ์ฒ๋ฆฌํด์:
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT ํ ํฐ์ "Bearer token" ํ์์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("JWT ํ ํฐ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค");
} catch (ExpiredJwtException e) {
System.out.println("JWT ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค");
}
} else {
logger.warn("JWT ํ ํฐ์ด Bearer๋ก ์์ํ์ง ์์ต๋๋ค");
}
// ํ ํฐ์ด ์ ํจํ๋ฉด Spring Security ์ปจํ
์คํธ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ค์ ํฉ๋๋ค
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
๋ง์ง๋ง์ผ๋ก Security ์ค์ ์ JWT ์ธ์ฆ์ ๋ง๊ฒ ์์ ํด์:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// JWT ํํฐ ์ถ๊ฐ
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
์ด์ ๋ก๊ทธ์ธ API๋ฅผ ๋ง๋ค์ด JWT ํ ํฐ์ ๋ฐ๊ธํด๋ณผ๊ฒ์:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/login")
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
์ด๋ ๊ฒ JWT ์ธ์ฆ์ ๊ตฌํํ๋ฉด RESTful API์ ์์ ํ๊ฒ ์ ๊ทผํ ์ ์์ด์. ํด๋ผ์ด์ธํธ๋ ๋ก๊ทธ์ธ API๋ฅผ ํตํด ํ ํฐ์ ๋ฐ๊ณ , ์ดํ ์์ฒญ์์ ์ด ํ ํฐ์ Authorization ํค๋์ ํฌํจ์์ผ ๋ณด๋ด๋ฉด ๋ผ์!
9. ์ค์ ํ๋ก์ ํธ: ๊ฐ๋จํ API ์๋น์ค ๊ตฌํ ๐
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก ๊ฐ๋จํ API ์๋น์ค๋ฅผ ๊ตฌํํด๋ณผ๊ฒ์! ์ด๋ฒ์๋ ํ ์ผ ๊ด๋ฆฌ(Todo) API๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์.
ํ๋ก์ ํธ ๊ตฌ์กฐ ๐
src/main/java/com/example/restapidemo/
โโโ controller/
โ โโโ TodoController.java
โ โโโ AuthController.java
โโโ model/
โ โโโ Todo.java
โ โโโ User.java
โโโ repository/
โ โโโ TodoRepository.java
โ โโโ UserRepository.java
โโโ service/
โ โโโ TodoService.java
โ โโโ UserService.java
โโโ security/
โ โโโ JwtTokenUtil.java
โ โโโ JwtRequestFilter.java
โโโ exception/
โ โโโ ResourceNotFoundException.java
โ โโโ GlobalExceptionHandler.java
โโโ dto/
โ โโโ TodoDto.java
โ โโโ ApiResponse.java
โโโ RestApiDemoApplication.java
Todo ๋ชจ๋ธ ๋ง๋ค๊ธฐ ๐
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "์ ๋ชฉ์ ํ์ ํญ๋ชฉ์
๋๋ค.")
private String title;
private String description;
private boolean completed;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
Todo ๋ฆฌํฌ์งํ ๋ฆฌ ๋ง๋ค๊ธฐ ๐๏ธ
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
List<Todo> findByUserIdOrderByCreatedAtDesc(Long userId);
List<Todo> findByUserIdAndCompletedOrderByCreatedAtDesc(Long userId, boolean completed);
}
Todo ์๋น์ค ๋ง๋ค๊ธฐ โ๏ธ
@Service
public class TodoService {
private final TodoRepository todoRepository;
private final UserRepository userRepository;
@Autowired
public TodoService(TodoRepository todoRepository, UserRepository userRepository) {
this.todoRepository = todoRepository;
this.userRepository = userRepository;
}
public List<Todo> getAllTodosByUser(Long userId) {
return todoRepository.findByUserIdOrderByCreatedAtDesc(userId);
}
public List<Todo> getTodosByUserAndStatus(Long userId, boolean completed) {
return todoRepository.findByUserIdAndCompletedOrderByCreatedAtDesc(userId, completed);
}
public Todo getTodoById(Long id, Long userId) {
Todo todo = todoRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("ID๊ฐ " + id + "์ธ ํ ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค."));
if (!todo.getUser().getId().equals(userId)) {
throw new AccessDeniedException("์ด ํ ์ผ์ ์ ๊ทผํ ๊ถํ์ด ์์ต๋๋ค.");
}
return todo;
}
public Todo createTodo(TodoDto todoDto, Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("ID๊ฐ " + userId + "์ธ ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
Todo todo = new Todo();
todo.setTitle(todoDto.getTitle());
todo.setDescription(todoDto.getDescription());
todo.setCompleted(false);
todo.setUser(user);
return todoRepository.save(todo);
}
public Todo updateTodo(Long id, TodoDto todoDto, Long userId) {
Todo todo = getTodoById(id, userId);
todo.setTitle(todoDto.getTitle());
todo.setDescription(todoDto.getDescription());
todo.setCompleted(todoDto.isCompleted());
return todoRepository.save(todo);
}
public void deleteTodo(Long id, Long userId) {
Todo todo = getTodoById(id, userId);
todoRepository.delete(todo);
}
}
Todo ์ปจํธ๋กค๋ฌ ๋ง๋ค๊ธฐ ๐ฎ
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoService todoService;
@Autowired
public TodoController(TodoService todoService) {
this.todoService = todoService;
}
@GetMapping
public ResponseEntity<ApiResponse<List<Todo>>> getAllTodos(
@RequestParam(required = false) Boolean completed,
Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User user = ((CustomUserDetails) userDetails).getUser();
List<Todo> todos;
if (completed != null) {
todos = todoService.getTodosByUserAndStatus(user.getId(), completed);
} else {
todos = todoService.getAllTodosByUser(user.getId());
}
ApiResponse<List<Todo>> response = new ApiResponse<>(true, "ํ ์ผ ๋ชฉ๋ก์ ์กฐํํ์ต๋๋ค.", todos);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Todo>> getTodoById(
@PathVariable Long id,
Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User user = ((CustomUserDetails) userDetails).getUser();
Todo todo = todoService.getTodoById(id, user.getId());
ApiResponse<Todo> response = new ApiResponse<>(true, "ํ ์ผ์ ์กฐํํ์ต๋๋ค.", todo);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@PostMapping
public ResponseEntity<ApiResponse<Todo>> createTodo(
@Valid @RequestBody TodoDto todoDto,
Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User user = ((CustomUserDetails) userDetails).getUser();
Todo createdTodo = todoService.createTodo(todoDto, user.getId());
ApiResponse<Todo> response = new ApiResponse<>(true, "ํ ์ผ์ ์์ฑํ์ต๋๋ค.", createdTodo);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Todo>> updateTodo(
@PathVariable Long id,
@Valid @RequestBody TodoDto todoDto,
Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User user = ((CustomUserDetails) userDetails).getUser();
Todo updatedTodo = todoService.updateTodo(id, todoDto, user.getId());
ApiResponse<Todo> response = new ApiResponse<>(true, "ํ ์ผ์ ์์ ํ์ต๋๋ค.", updatedTodo);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteTodo(
@PathVariable Long id,
Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User user = ((CustomUserDetails) userDetails).getUser();
todoService.deleteTodo(id, user.getId());
ApiResponse<Void> response = new ApiResponse<>(true, "ํ ์ผ์ ์ญ์ ํ์ต๋๋ค.", null);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋จํ ํ ์ผ ๊ด๋ฆฌ API๊ฐ ์์ฑ๋ผ์! ์ด API๋ ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ์ ๊ณตํด์:
- ์ฌ์ฉ์๋ณ ํ ์ผ ๋ชฉ๋ก ์กฐํ
- ์๋ฃ/๋ฏธ์๋ฃ ์ํ๋ณ ํ ์ผ ํํฐ๋ง
- ํ ์ผ ์์ธ ์กฐํ
- ์๋ก์ด ํ ์ผ ์์ฑ
- ํ ์ผ ์์
- ํ ์ผ ์ญ์
๋ํ JWT ์ธ์ฆ์ ํตํด ์ฌ์ฉ์๋ณ๋ก ์์ ์ ํ ์ผ๋ง ์ ๊ทผํ ์ ์๋๋ก ๋ณด์์ด ์ ์ฉ๋์ด ์์ด์. ์ด๋ฐ API๋ ํ ์ผ ๊ด๋ฆฌ ์ฑ์ ๋ฐฑ์๋๋ก ํ์ฉํ ์ ์์ด์!
์ฌ๋ฅ๋ท์์๋ ์ด๋ฐ ์ข ๋ฅ์ API ๊ฐ๋ฐ ์ฌ๋ฅ์ด ์ธ๊ธฐ๊ฐ ๋ง์ ๊ฒ ๊ฐ๋ค์. ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ค์ด ์ด๋ฐ ๋ฐฑ์๋ API๋ฅผ ํ์ฉํด ๋ค์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ผ๋๊น์! ๐
10. ๋ง๋ฌด๋ฆฌ ๋ฐ ๋ค์ ๋จ๊ณ ๐ฌ
์ง๊ธ๊น์ง Spring MVC๋ฅผ ์ฌ์ฉํด RESTful API๋ฅผ ๊ฐ๋ฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ดค์ด์. ์ ๋ง ๋ง์ ๋ด์ฉ์ ๋ค๋ค๋ค์! ๐
๋ฐฐ์ด ๋ด์ฉ ์ ๋ฆฌ ๐
- Spring MVC์ RESTful API ๊ฐ๋ ์ดํดํ๊ธฐ
- ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ ํ๊ธฐ
- ์ฒซ ๋ฒ์งธ RESTful API ๋ง๋ค๊ธฐ
- HTTP ๋ฉ์๋ ํ์ฉํ๊ธฐ
- ์์ฒญ/์๋ต ์ฒ๋ฆฌํ๊ธฐ
- ์์ธ ์ฒ๋ฆฌ์ ์ ํจ์ฑ ๊ฒ์ฌ
- API ๋ฌธ์ํํ๊ธฐ
- ๋ณด์ ์ ์ฉํ๊ธฐ
- ์ค์ ํ๋ก์ ํธ: ๊ฐ๋จํ API ์๋น์ค ๊ตฌํ
๋ค์ ๋จ๊ณ๋ก ๋ฐฐ์ธ ์ ์๋ ๋ด์ฉ ๐
RESTful API ๊ฐ๋ฐ์ ๋ ๊น์ด ๋ค์ด๊ฐ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฃผ์ ๋ฅผ ๊ณต๋ถํด๋ณด์ธ์:
- HATEOAS: Hypermedia As The Engine Of Application State - API ์๋ต์ ๊ด๋ จ ๋ฆฌ์์ค ๋งํฌ๋ฅผ ํฌํจ์์ผ ํด๋ผ์ด์ธํธ๊ฐ API๋ฅผ ๋ ์ฝ๊ฒ ํ์ํ ์ ์๊ฒ ํ๋ ๊ธฐ์
- API ๋ฒ์ ๊ด๋ฆฌ: API๊ฐ ๋ฐ์ ํจ์ ๋ฐ๋ผ ์ด์ ๋ฒ์ ๊ณผ์ ํธํ์ฑ์ ์ ์งํ๋ ๋ฐฉ๋ฒ
- ์บ์ฑ: API ์๋ต์ ์บ์ฑํ์ฌ ์ฑ๋ฅ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ
- ๋น๋๊ธฐ API: WebFlux๋ฅผ ์ฌ์ฉํ ๋น๋๊ธฐ RESTful API ๊ฐ๋ฐ
- ๋ง์ดํฌ๋ก์๋น์ค: ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ ์ค๊ณ
๋ง๋ฌด๋ฆฌ ์ธ์ฌ ๐
์ด ๊ธ์ด ์ฌ๋ฌ๋ถ์ Spring MVC RESTful API ๊ฐ๋ฐ์ ๋์์ด ๋์์ผ๋ฉด ์ข๊ฒ ์ด์! ๊ฐ๋ฐ์ ๊ณ์ ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ๋ ๊ณผ์ ์ด๋, ๊พธ์คํ ๊ณต๋ถํ๊ณ ์ค์ตํด๋ณด์ธ์.
ํน์ API ๊ฐ๋ฐ์ ๊ดํ ๋ ๊น์ ์ง์์ด๋ ์ค๋ฌด ๊ฒฝํ์ด ํ์ํ์๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ๊ด๋ จ ์ฌ๋ฅ์ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์์. ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ์ ๋ ๋น ๋ฅด๊ฒ ์ฑ์ฅํ ์ ์์ผ๋๊น์! ๐
๋ค์์ ๋ ๋ค๋ฅธ ์ฃผ์ ๋ก ์ฐพ์์ฌ๊ฒ์. ๊ทธ๋๊น์ง ์ฆ๊ฑฐ์ด ์ฝ๋ฉํ์ธ์! ๐จโ๐ป๐ฉโ๐ป
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.