일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 플러터
- 애니메이션
- AnimationController
- Firebase
- 마우이
- MVVM
- 닷넷
- HTML
- 오류
- React JS
- db
- 자바스크립트
- 리엑트
- Animation
- MSSQL
- spring boot
- 깃허브
- Binding
- typescript
- 파이어베이스
- listview
- GitHub
- JavaScript
- 함수
- Flutter
- 바인딩
- page
- MS-SQL
- Maui
- .NET
- Today
- Total
개발노트
11. [Spring Boot] DTO 사용하기 (JSON 데이터 커스텀하기) 본문
DTO.
DTO(Data Transfer Object)는 데이터 전송을 위해 사용되는 객체로, 일반적으로 엔티티(Entity)의 일부 데이터를 전달하는 데 사용됩니다. DTO는 비즈니스 로직을 포함하지 않고, 순수하게 데이터를 전송하거나 전달하기 위한 용도로 설계됩니다.
DTO의 특징
- 데이터 전송 및 전달 용도: DTO는 주로 시스템의 다른 부분 간에 데이터를 전송하거나 전달하는 용도로 사용됩니다. 네트워크를 통해 클라이언트와 서버, 혹은 서버 간에 데이터를 주고받을 때 사용할 수 있습니다.
- 데이터 전송 최적화: DTO는 엔티티의 일부 데이터만을 포함하므로, 전송할 데이터 양을 최적화할 수 있습니다. 이는 성능 향상과 데이터 사용량 절감에 도움이 됩니다.
- 클라이언트 요구사항 대응: 클라이언트가 필요로 하는 특정 데이터를 DTO로 전달함으로써, 엔티티의 모든 데이터를 노출하지 않고 필요한 부분만 전달할 수 있습니다.
DTO의 장점
- 데이터 은닉: DTO를 사용하면 엔티티의 내부 구조를 숨길 수 있어서 클라이언트에게 불필요한 정보를 노출시키지 않습니다.
- 성능 향상: DTO를 사용하여 전송할 데이터 양을 최적화하고, 네트워크 부하를 줄일 수 있습니다.
- 유연성: DTO는 엔티티의 구조와 별개로 설계되기 때문에, 클라이언트의 요구에 맞게 유연하게 변경할 수 있습니다.
DTO는 주로 비즈니스 로직을 포함하지 않고, 데이터 전송 및 전달에만 집중하는 객체로 사용되므로, 엔티티와 DTO 간의 매핑 및 변환을 위한 작업이 필요할 수 있습니다.
아래와 같이 JSON 데이터를 얻고 싶을 때 DTO를 만들어 JSON 데이터를 커스텀 할 수 있습니다.
제가 원하는 데이터는 아래와 같습니다.
- 칵테일 식별키인 cocktail_id로 특정 칵테일의 정보를 가져오고 싶다.
- 칵테일 정보에는 만드는 방법 즉, 레시피(Recipe)가 포함되어있다.
- 레시피에는 재료이름(ingredient의 name)이 포함되어야한다.
현재 얻고자하는 JSON 데이터.
Table 간의 관계
구현하고자하는 JOIN 쿼리
Select *
From cocktail AS C
LEFT JOIN recipe AS R ON C.cocktail_id = R.cocktail_id
LEFT JOIN ingredient AS I ON I.ingredient_id = R.ingredient_id
where C.cocktail_id = 3;
조회된 시트
이와 같은 결과를 얻기위해 DTO를 만들어
1. Entity간의 관계맵핑(Entity Layer)
Recipe
@Entity
@Getter
@Setter
@Table(name = "recipe")
public class Recipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "recipe_id")
private Long recipeId;
// Many-to-One 관계 설정: Recipe 엔티티는 Cocktail 엔티티에 매핑됨
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore // JSON 직렬화/역직렬화 시 해당 필드를 무시하도록 지정
@JoinColumn(name = "cocktail_id") // 외래 키를 가리키는 칼럼명 지정
private Cocktail cocktail;
// Many-to-One 관계 설정: Recipe 엔티티는 Ingredient 엔티티에 매핑됨
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore // JSON 직렬화/역직렬화 시 해당 필드를 무시하도록 지정
@JoinColumn(name = "ingredient_id") // 외래 키를 가리키는 칼럼명 지정
private Ingredient ingredient;
// 이 외에 필요한 필드들
...
}
- @ManyToOne(fetch = FetchType.LAZY): Many-to-One 관계를 설정합니다. 여기서 Recipe 엔티티는 Cocktail 엔티티와 Ingredient 엔티티에 각각 Many-to-One 관계로 매핑됩니다.
- @JsonIgnore: Jackson 라이브러리의 @JsonIgnore 어노테이션을 사용하여 JSON 직렬화/역직렬화 시 해당 필드를 무시하도록 지정합니다. 이는 무한 순환 참조나 불필요한 데이터 노출을 방지하기 위한 것입니다.
- @JoinColumn(name = "cocktail_id"), @JoinColumn(name = "ingredient_id"): 외래 키(Foreign Key)를 가리키는 칼럼명을 지정합니다. **cocktail_id**는 Cocktail 엔티티의 주 키를 참조하고, **ingredient_id**는 Ingredient 엔티티의 주 키를 참조합니다.
위의 Recipe 엔티티는 Cocktail과 Ingredient 엔티티와의 관계를 설정하고, 각각의 관계를 지연 로딩(FetchType.LAZY)으로 설정하여 필요할 때만 연관된 엔티티를 로딩하도록 합니다. 이를 통해 성능을 최적화할 수 있습니다.
Cocktail
@Entity
@Getter
@Setter
@Table(name = "cocktail")
public class Cocktail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cocktail_id")
private Long cocktailId;
// One-to-Many 관계 설정: Cocktail 엔티티는 Recipe 엔티티와 One-to-Many 관계를 가짐
@OneToMany(mappedBy = "cocktail", fetch = FetchType.LAZY)
@ToString.Exclude // Lombok의 @ToString 어노테이션에서 해당 필드를 제외하여 toString() 메서드에서 순환 참조를 방지함
private List<Recipe> recipeList = new ArrayList<>();
// 이 외에 필요한 필드들
...
}
- @OneToMany(mappedBy = "cocktail", fetch = FetchType.LAZY): One-to-Many 관계를 설정합니다. 여기서 Cocktail 엔티티는 Recipe 엔티티와 One-to-Many 관계로 매핑됩니다. **mappedBy = "cocktail"**은 Recipe 엔티티의 cocktail 필드를 통해 매핑된다는 것을 나타냅니다.
- @ToString.Exclude: Lombok의 @ToString 어노테이션에서 해당 필드를 제외하여 toString() 메서드에서 순환 참조를 방지합니다. 이는 Recipe 엔티티가 Cocktail 엔티티를 참조하고 있고, Cocktail 엔티티가 다시 Recipe 엔티티를 참조하는 상황에서 무한 순환 참조를 방지하기 위해 사용됩니다.
위의 코드에서 Cocktail 엔티티는 Recipe 엔티티와 One-to-Many 관계를 가지며, recipeList 필드를 통해 여러 개의 Recipe 엔티티를 관리합니다. 이 관계에서는 Cocktail 엔티티를 조회할 때 연관된 Recipe 엔티티들을 지연 로딩(FetchType.LAZY)으로 가져옵니다. 이를 통해 성능을 최적화하고 필요할 때만 연관된 엔티티를 로딩할 수 있습니다.
Ingredient
@Entity
@Getter
@Setter
@Table(name = "ingredient")
public class Ingredient {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ingredient_id")
private Long ingredientId;
// One-to-Many 관계 설정: Ingredient 엔티티는 Recipe 엔티티와 One-to-Many 관계를 가짐
@OneToMany(mappedBy = "ingredient", fetch = FetchType.LAZY)
@ToString.Exclude // Lombok의 @ToString 어노테이션에서 해당 필드를 제외하여 toString() 메서드에서 순환 참조를 방지함
private List<Recipe> recipeList = new ArrayList<>();
// 이 외에 필요한 필드들
...
}
- @OneToMany(mappedBy = "ingredient", fetch = FetchType.LAZY): One-to-Many 관계를 설정합니다. 여기서 Ingredient 엔티티는 Recipe 엔티티와 One-to-Many 관계로 매핑됩니다. **mappedBy = "ingredient"**은 Recipe 엔티티의 ingredient 필드를 통해 매핑된다는 것을 나타냅니다.
- @ToString.Exclude: Lombok의 @ToString 어노테이션에서 해당 필드를 제외하여 toString() 메서드에서 순환 참조를 방지합니다. 이는 Recipe 엔티티가 Ingredient 엔티티를 참조하고 있고, Ingredient 엔티티가 다시 Recipe 엔티티를 참조하는 상황에서 무한 순환 참조를 방지하기 위해 사용됩니다.
위의 코드에서 Ingredient 엔티티는 Recipe 엔티티와 One-to-Many 관계를 가지며, recipeList 필드를 통해 여러 개의 Recipe 엔티티를 관리합니다. 이 관계에서는 Ingredient 엔티티를 조회할 때 연관된 Recipe 엔티티들을 지연 로딩(FetchType.LAZY)으로 가져옵니다. 이를 통해 성능을 최적화하고 필요할 때만 연관된 엔티티를 로딩할 수 있습니다.
2. Inferface 작성 (Repository Layer)
CocktailRepository
@Repository
public interface CocktailRepository extends JpaRepository<Cocktail,Long> {
}
3. DTO 정의하기
CocktailsDTO, IngredientDTO
package com.homebar.apiserver.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CocktailsDTO {
private Long cocktailId;
private String name;
private String nameEng;
private String detail;
private double sweetness;
private double acidity;
private double strength;
private int state;
private List<IngredientDTO> ingredients;
@Getter
@Builder
public static class IngredientDTO{
private String name;
private double amount;
}
}
위의 코드는 DTO (Data Transfer Object) 클래스인 **CocktailsDTO**와 그 안에 내부 클래스인 **IngredientDTO**를 정의하고 있습니다. 이 클래스들은 주로 데이터 전송을 위해 사용되며, 데이터를 간단하고 구조화된 형태로 전달하기 위해 설계됩니다.
CocktailsDTO 클래스
- @Getter: Lombok 어노테이션으로, 모든 필드에 대해 getter 메서드를 자동으로 생성합니다.
- @AllArgsConstructor: Lombok 어노테이션으로, 모든 필드를 매개변수로 받는 생성자를 자동으로 생성합니다.
- @NoArgsConstructor: Lombok 어노테이션으로, 매개변수가 없는 기본 생성자를 자동으로 생성합니다.
- @Builder: Lombok 어노테이션으로, 빌더 패턴을 지원하여 객체를 생성할 때 객체 초기화를 보다 명확하게 할 수 있도록 합니다.
필드 설명
- cocktailId: 칵테일의 고유 식별자 (ID)
- name: 칵테일 이름
- nameEng: 칵테일 영문 이름
- detail: 칵테일에 대한 상세 설명
- sweetness: 칵테일의 당도
- acidity: 칵테일의 산도
- strength: 칵테일의 도수 (알코올 도수)
- state: 칵테일 상태를 나타내는 정수 값 (예: 활성화, 비활성화 등)
- ingredients: 칵테일에 사용된 재료 목록을 담고 있는 List<IngredientDTO> 타입의 필드
IngredientDTO 내부 클래스
CocktailsDTO 클래스 내부에 정의된 내부 클래스로, 칵테일 재료를 나타내는 DTO 클래스입니다.
필드 설명
- name: 재료 이름 (Ingredient의 name)
- amount: 재료의 양 또는 비율(Recipe의 amount)
주요 특징
- @Getter: 내부 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성합니다.
- @Builder: 내부 클래스에서도 빌더 패턴을 사용하여 객체를 생성할 수 있도록 합니다.
이런 구조로 설계된 DTO 클래스는 주로 REST API 등을 통해 클라이언트와 서버 간에 데이터를 전달할 때 사용됩니다. 각 필드는 해당하는 데이터를 간결하게 표현하고, 내부 클래스를 사용하여 관련 데이터를 그룹화하여 전송할 수 있습니다. Lombok의 어노테이션을 활용하면 보일러플레이트 코드를 줄이고 간단하게 DTO 클래스를 구현할 수 있습니다.
**@AllArgsConstructor**과 **@NoArgsConstructor**은 롬복(Lombok) 라이브러리에서 제공하는 어노테이션으로, 자바 클래스의 생성자를 자동으로 생성해주는 기능을 제공합니다.
@AllArgsConstructor
**@AllArgsConstructor**은 모든 필드를 매개변수로 받는 생성자를 자동으로 생성해줍니다. 해당 클래스의 모든 필드를 매개변수로 받아서 객체를 생성할 때 필드 값을 설정할 수 있는 생성자를 자동으로 생성합니다.
예를 들어, 다음과 같이 클래스에 **@AllArgsConstructor**을 적용하면 모든 필드를 매개변수로 받는 생성자가 자동으로 생성됩니다.
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
public class Person {
private String name;
private int age;
private String address;
}
// 자동 생성된 생성자
// public Person(String name, int age, String address) {
// this.name = name;
// this.age = age;
// this.address = address;
// }
위의 예시에서 Person 클래스에 **@AllArgsConstructor**이 적용되었으므로, name, age, address 필드를 모두 매개변수로 받는 생성자가 자동으로 생성됩니다.
@NoArgsConstructor
**@NoArgsConstructor**은 매개변수가 없는 기본 생성자를 자동으로 생성해줍니다. 이 클래스의 객체를 매개변수 없이 생성할 때 사용할 수 있는 기본 생성자를 자동으로 생성합니다.
예를 들어, 다음과 같이 클래스에 **@NoArgsConstructor**을 적용하면 매개변수가 없는 기본 생성자가 자동으로 생성됩니다.
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
@Getter
@Setter
@NoArgsConstructor
public class Person {
private String name;
private int age;
private String address;
}
// 자동 생성된 기본 생성자
// public Person() {
// // 기본 생성자의 내용
// }
위의 예시에서 Person 클래스에 **@NoArgsConstructor**이 적용되었으므로, 매개변수가 없는 기본 생성자가 자동으로 생성됩니다.
**@AllArgsConstructor**과 **@NoArgsConstructor**을 함께 사용하면 필요에 따라 객체를 매개변수로 초기화하거나 매개변수 없이 기본 생성자를 사용하여 객체를 생성할 수 있습니다. 이는 코드의 가독성을 높이고 개발 생산성을 향상시키는데 도움이 됩니다.
4. Entity > DTO 변환(Service Layer)
CocktailService
@Service
public class CocktailService {
@Autowired
private CocktailRepository cocktailRepository;
public CocktailsDTO mapToDTP(Cocktail cocktail){
return CocktailsDTO.builder()
.cocktailId(cocktail.getCocktailId())
.name(cocktail.getName())
.nameEng(cocktail.getName_eng())
.detail(cocktail.getDetail())
.sweetness(cocktail.getSweetness())
.acidity(cocktail.getAcidity())
.strength(cocktail.getStrength())
.state(cocktail.getState())
.ingredients(cocktail.getRecipeList().stream()
.map(recipe -> CocktailsDTO.IngredientDTO.builder()
.name(recipe.getIngredient().getName())
.amount(recipe.getAmount()).build())
.collect(Collectors.toList()))
.build();
}
}
위의 코드는 CocktailService 클래스에서 Cocktail 엔티티를 **CocktailsDTO**로 변환하는 과정을 담은 mapToDTP() 메서드를 보여줍니다. 이 과정을 간단히 설명하겠습니다.
- 메서드 시그니처: public CocktailsDTO mapToDTP(Cocktail cocktail)
- 이 메서드는 Cocktail 엔티티를 입력으로 받아서, 해당 엔티티를 CocktailsDTO 객체로 변환하여 반환합니다.
- DTO 객체 생성: CocktailsDTO.builder()
- Lombok의 @Builder 어노테이션을 사용하여 **CocktailsDTO**의 빌더 객체를 생성합니다. 이를 통해 빌더 패턴을 사용하여 CocktailsDTO 객체를 생성하고 초기화합니다.
- 필드 설정:
- @Getter를 이용하여 생성된 get 메소드로 Entity 값을 DTO 필드에 설정합니다.
- cocktailId(cocktail.getCocktailId()): Cocktail 엔티티의 cocktailId 값을 **CocktailsDTO**의 cocktailId 필드에 설정합니다.
- name(cocktail.getName()): Cocktail 엔티티의 name 값을 **CocktailsDTO**의 name 필드에 설정합니다.
- nameEng(cocktail.getName_eng()): Cocktail 엔티티의 name_eng 값을 **CocktailsDTO**의 nameEng 필드에 설정합니다.
- detail(cocktail.getDetail()): Cocktail 엔티티의 detail 값을 **CocktailsDTO**의 detail 필드에 설정합니다.
- sweetness(cocktail.getSweetness()): Cocktail 엔티티의 sweetness 값을 **CocktailsDTO**의 sweetness 필드에 설정합니다.
- acidity(cocktail.getAcidity()): Cocktail 엔티티의 acidity 값을 **CocktailsDTO**의 acidity 필드에 설정합니다.
- strength(cocktail.getStrength()): Cocktail 엔티티의 strength 값을 **CocktailsDTO**의 strength 필드에 설정합니다.
- state(cocktail.getState()): Cocktail 엔티티의 state 값을 **CocktailsDTO**의 state 필드에 설정합니다.
- ingredients 필드 설정:
- cocktail.getRecipeList().stream().map(...): Cocktail 엔티티의 **recipeList**를 스트림으로 변환하고, 각 Recipe 엔티티를 **CocktailsDTO.IngredientDTO**로 매핑합니다.
- recipe.getIngredient().getName(): 각 Recipe 엔티티에서 Ingredient 엔티티의 name 필드를 가져와서 **IngredientDTO**의 name 필드에 설정합니다.
- recipe.getAmount(): 각 Recipe 엔티티에서 amount 필드를 가져와서 **IngredientDTO**의 amount 필드에 설정합니다.
- .collect(Collectors.toList()): 매핑된 IngredientDTO 객체들을 리스트로 수집합니다.
- .ingredients(...): 수집된 IngredientDTO 리스트를 **CocktailsDTO**의 ingredients 필드에 설정합니다.
- 빌더 객체를 통한 객체 생성: .build()
- 모든 필드 설정이 완료된 CocktailsDTO 객체를 빌더를 통해 생성합니다.
@Getter 어노테이션은 여기서는 사용되지 않았습니다. @Getter 어노테이션은 필드에 대한 getter 메서드를 자동으로 생성할 때 사용됩니다. 여기서는 Lombok의 @Builder 어노테이션을 사용하여 빌더 패턴을 활용하여 객체를 생성하고 초기화하였습니다. @Getter 어노테이션은 필드에 대한 getter 메서드를 생성할 때 사용되는 것이므로, **@Builder**와 함께 사용되는 것은 아닙니다.
stream()
stream 은 Java 8부터 추가된 기능으로, 컬렉션과 배열 등의 요소를 반복적으로 처리할 수 있는 기능을 제공합니다. **stream**을 사용하면 데이터를 필터링하거나 매핑, 정렬, 그룹화 등 다양한 작업을 수행할 수 있습니다.
Stream의 주요 특징과 기능:
- 컬렉션과 배열에서 사용 가능:
- Collection 인터페이스의 하위 인터페이스인 List, Set, Map 등과 배열에서 stream() 메서드를 호출하여 Stream 객체를 생성할 수 있습니다.
- 중간 연산과 최종 연산:
- **Stream**은 중간 연산과 최종 연산으로 구성됩니다.
- 중간 연산(Intermediate Operations): filter, map, flatMap, sorted, distinct 등의 연산으로 스트림의 요소를 변환하거나 필터링할 수 있습니다.
- 최종 연산(Terminal Operations): collect, forEach, count, reduce, anyMatch, allMatch, noneMatch 등의 연산으로 스트림의 요소를 소모하고 결과를 반환합니다.
- 지연 연산(Lazy Evaluation):
- **Stream**은 지연 연산을 지원하여 최종 연산이 호출되기 전까지 중간 연산이 실제로 수행되지 않습니다. 이를 통해 효율적인 데이터 처리가 가능합니다.
5. Request 메소드 작성(Controller Layer)
CocktailController
@RestController
@RequestMapping("/cocktails")
public class CocktailController {
@Autowired
private CocktailService cocktailService;
/**
* 주어진 칵테일 ID에 해당하는 칵테일의 레시피 정보를 반환하는 엔드포인트.
*
* @param cocktailId 조회할 칵테일의 ID
* @return 칵테일의 레시피 정보를 담은 CocktailsDTO 객체
*/
@GetMapping("/recipe/{cocktail_id}")
CocktailsDTO getCocktailRecipe(@PathVariable("cocktail_id") Long cocktailId) {
// 주어진 ID로 칵테일을 조회하고, 존재하지 않는 경우 null 반환
Cocktail cocktail = cocktailService.findById(cocktailId).orElse(null);
if (cocktail == null) {
return null; // 칵테일이 존재하지 않는 경우 null 반환
} else {
// 조회된 칵테일을 CocktailsDTO로 매핑하여 반환
return cocktailService.mapToDTP(cocktail);
}
}
// 위와 같은 소스 //
// @GetMapping("/recipe/{cocktail_id}")
// CocktailsDTO getCocktailRecipe(@PathVariable("cocktail_id") Long cocktailId) {
// return cocktailService.findById(cocktailId)
// .map(cocktailService::mapToDTP)
// .orElse(null);
// }
}
위의 코드는 CocktailController 클래스에서 /cocktails/recipe/{cocktail_id} 엔드포인트를 처리하는 메서드를 보여줍니다. 각 주석에 대한 설명은 다음과 같습니다:
- @RestController: 해당 클래스가 REST API의 컨트롤러임을 나타내는 어노테이션입니다.
- @RequestMapping("/cocktails"): 이 컨트롤러의 기본 URL 경로를 **/cocktails**로 지정합니다.
- @Autowired private CocktailService cocktailService;: CocktailService 타입의 빈을 자동으로 주입받습니다.
- @GetMapping("/recipe/{cocktail_id}"): HTTP GET 요청을 처리하는 핸들러 메서드를 정의합니다. 요청 경로는 **/cocktails/recipe/{cocktail_id}**이며, **{cocktail_id}**는 변수로 받습니다.
- @PathVariable("cocktail_id") Long cocktailId: URL 경로에서 추출된 cocktail_id 파라미터를 메서드의 매개변수로 받습니다.
- CocktailsDTO getCocktailRecipe(Long cocktailId): 주어진 **cocktailId**에 해당하는 칵테일의 레시피 정보를 반환하는 메서드입니다. 메서드의 반환 타입은 **CocktailsDTO**입니다.
- cocktailService.findById(cocktailId).orElse(null);: **cocktailService**를 사용하여 주어진 **cocktailId**로 칵테일을 조회합니다. 만약 조회된 칵테일이 없으면 **null**을 반환합니다.
- cocktail == null: 조회된 칵테일이 **null**인 경우, **null**을 반환하여 클라이언트에게 칵테일이 존재하지 않음을 알립니다.
- cocktailService.mapToDTP(cocktail): 조회된 칵테일을 **CocktailsDTO**로 매핑하여 반환합니다. mapToDTP 메서드는 **cocktailService**에 의해 제공되며, 주어진 Cocktail 객체를 CocktailsDTO 객체로 변환합니다.
이 엔드포인트는 클라이언트가 /cocktails/recipe/{cocktail_id} 경로로 GET 요청을 보내면 해당하는 칵테일의 레시피 정보를 반환합니다. 요청된 칵테일이 존재하지 않는 경우 **null**을 반환합니다.
GET 호출 결과.
(http://localhost:8080/cocktails/recipe/{cocktail_id})
'서버 개발 > Spring Boot' 카테고리의 다른 글
12. [Spring Boot] 이유 없이 갑자기 디펜던시(Dependency) 적용이 안된다면? (0) | 2024.06.24 |
---|---|
10. [Spring Boot] Entity 관계 매핑하기(@ManyToOne, @OneToMnay) (0) | 2024.05.13 |
9. [Spring Boot] 쿼리 메소드 (Repository 안의 메소드명에 따라 쿼리가 달라진다.) (0) | 2024.05.09 |
8. [Spring Boot] @ManyToOne 무한재귀 호출 문제 해결하기 (0) | 2024.04.30 |
7. [Spring Boot] PropertyReferenceException 에러 해결 (Java "_" 언더바 인식 못함 문제) (0) | 2024.04.30 |