Skip to content

Commit

Permalink
feat: Product 상세 조회 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Ogu1208 committed Aug 10, 2024
1 parent 29fb905 commit 7ece7f7
Show file tree
Hide file tree
Showing 13 changed files with 72 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class SecurityConfiguration(
"/v1/members/login", "/signup", "/v1/members/email/**", "/v1/access",
"/v1/categories/**", "/v1/products/**", "/v1/productLines/**", "/v2/productLines/**",
"/productLinePagingSlice", "/productLinePagingOffset",
"/v3/products",
"/v3/products/**",
"/v1/orderdetails", "/v1/orders", "membersPagingOffset", "membersPagingSlice",
"/v1/orderdetails", "/v1/orders", "/v2/orders", "/v3/orders", "/v1/orders/list",
"/v1/orders/list", "/ordersPagingOffset", "/ordersPagingSlice", "/v2/orders/list",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@ import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import org.store.clothstar.category.service.CategoryService
import org.store.clothstar.common.dto.MessageDTO
import org.store.clothstar.product.dto.request.ProductCreateRequest
import org.store.clothstar.product.dto.response.ProductResponse
import org.store.clothstar.product.service.ProductApplicationService
import org.store.clothstar.product.service.ProductService
import java.net.URI

@Tag(name = "Products", description = "Products(상품 옵션) 관련 API 입니다.")
@RequestMapping("/v3/products")
@RestController
private class ProductController (
private val productApplicationService: ProductApplicationService,
private val productService: ProductService,
private val categoryService: CategoryService,
) {
) {
@PostMapping
@Operation(summary = "상품 등록",
description = "카테고리 아이디, 상품 이름, 내용, 가격, 상태를 입력하여 상품을 신규 등록한다.")
Expand All @@ -41,4 +37,11 @@ private class ProductController (
return ResponseEntity(messageDTO, HttpStatus.CREATED)

}

@GetMapping("/{productId}")
@Operation(summary = "상품 상세 조회", description = "상품 ID를 사용하여 특정 상품의 상세 정보를 조회한다.")
fun getProductDetails(@PathVariable productId: Long): ResponseEntity<ProductResponse> {
val productResponse = productApplicationService.getProductDetails(productId)
return ResponseEntity(productResponse, HttpStatus.OK)
}
}
3 changes: 3 additions & 0 deletions src/main/kotlin/org/store/clothstar/product/domain/Item.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.store.clothstar.product.domain

import jakarta.persistence.*
import org.hibernate.annotations.BatchSize
import org.store.clothstar.product.domain.type.DisplayStatus
import org.store.clothstar.product.domain.type.SaleStatus

Expand All @@ -22,6 +23,7 @@ import org.store.clothstar.product.domain.type.SaleStatus
* ]
* }
*/
@BatchSize(size = 20)
@Entity
class Item(
@Id
Expand All @@ -33,6 +35,7 @@ class Item(
val saleStatus: SaleStatus,
val displayStatus: DisplayStatus,

@BatchSize(size = 20)
@ElementCollection
@CollectionTable(
name = "item_attributes",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.store.clothstar.product.domain

import jakarta.persistence.Embeddable
import org.hibernate.annotations.BatchSize

/**
* { "optionId": 1, "name": "색상", "value": "중청", "valueId": 1 }
*/
@BatchSize(size = 20)
@Embeddable
data class ItemAttribute(
var optionId: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.store.clothstar.product.domain

import jakarta.persistence.*
import org.hibernate.annotations.BatchSize
import org.store.clothstar.product.domain.type.ProductColor


Expand All @@ -12,6 +13,7 @@ import org.store.clothstar.product.domain.type.ProductColor
* "productOption": 1
* }
*/
@BatchSize(size = 20)
@Entity
class OptionValue(
@Id
Expand Down
16 changes: 14 additions & 2 deletions src/main/kotlin/org/store/clothstar/product/domain/Product.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package org.store.clothstar.product.domain

import jakarta.persistence.*
import org.hibernate.annotations.BatchSize
import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.store.clothstar.common.entity.BaseEntity
import org.store.clothstar.product.domain.type.DisplayStatus
import org.store.clothstar.product.domain.type.ProductColor
import org.store.clothstar.product.domain.type.SaleStatus
import org.store.clothstar.product.dto.request.UpdateProductRequest

@BatchSize(size = 20)
@Entity
class Product(
@Id
Expand All @@ -26,15 +30,19 @@ class Product(
var price: Int,
// 색상 목록

@BatchSize(size = 20)
@ElementCollection(targetClass = ProductColor::class)
@CollectionTable(name = "product_colors", joinColumns = [JoinColumn(name = "product_id")])
@Enumerated(EnumType.STRING)
@Fetch(FetchMode.SUBSELECT)
var productColors: MutableSet<ProductColor> = mutableSetOf(),

// 이미지 목록
@Fetch(FetchMode.SUBSELECT)
@BatchSize(size = 20)
@ElementCollection
@CollectionTable(name = "product_image", joinColumns = [JoinColumn(name = "product_line_id")])
var imageList: MutableList<ProductImage> = mutableListOf(),
var imageList: MutableSet<ProductImage> = mutableSetOf(),

// 기타 정보
var saleCount: Long = 0,
Expand All @@ -44,11 +52,15 @@ class Product(
var saleStatus: SaleStatus, // 판매 상태

// 연관 관계 (1:N)
@Fetch(FetchMode.SUBSELECT)
@BatchSize(size = 20)
@OneToMany(mappedBy = "product", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var productOptions: MutableSet<ProductOption> = mutableSetOf(),

@Fetch(FetchMode.SUBSELECT)
@BatchSize(size = 20)
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
var items: MutableList<Item> = mutableListOf(),
var items: MutableSet<Item> = mutableSetOf(),
) : BaseEntity() {


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package org.store.clothstar.product.domain
import jakarta.persistence.Embeddable
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import org.hibernate.annotations.BatchSize
import org.store.clothstar.product.domain.type.ImageType

@BatchSize(size = 20)
@Embeddable
class ProductImage(
val url: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.store.clothstar.product.domain

import jakarta.persistence.*
import org.hibernate.annotations.BatchSize


/**
Expand All @@ -20,6 +21,7 @@ import jakarta.persistence.*
* ]
* }
*/
@BatchSize(size = 20)
@Entity
class ProductOption(
@Id
Expand All @@ -33,6 +35,7 @@ class ProductOption(
@JoinColumn(name = "product_id")
val product: Product,

@BatchSize(size = 20)
@OneToMany(mappedBy = "productOption", cascade = [CascadeType.ALL], orphanRemoval = true)
var optionValues: MutableList<OptionValue> = mutableListOf(),
var optionValues: MutableSet<OptionValue> = mutableSetOf(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ class ProductResponse(
) {
companion object {
fun from(product: Product): ProductResponse {
val distinctImages = product.imageList.distinctBy { it.url }
return ProductResponse(
id = product.productId!!,
name = product.name,
description = product.content,
imageList = distinctImages.map { ImageResponse.from(it) },
productColors = product.productColors.toSet(),
price = product.price,
displayStatus = product.displayStatus,
saleStatus = product.saleStatus,
productColors = product.productColors.toSet(),
imageList = product.imageList.map { ImageResponse.from(it) },
productOptions = product.productOptions.map { ProductOptionResponse.from(it) },
items = product.items.map { ItemResponse.from(it) }
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.store.clothstar.product.repository

import org.springframework.data.jpa.repository.EntityGraph
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.store.clothstar.product.domain.Product
import java.util.*

@Repository
interface ProductRepository : JpaRepository<Product, Long> {
fun findByProductIdIn(productLineIds: List<Long>): List<Product?>

@EntityGraph(attributePaths = ["productOptions", "items", "imageList"])
fun findWithDetailsByProductId(productId: Long): Optional<Product>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import org.springframework.web.multipart.MultipartFile
import org.store.clothstar.member.domain.Member
import org.store.clothstar.member.domain.vo.MemberShoppingActivity
import org.store.clothstar.member.service.MemberService
import org.store.clothstar.product.domain.Product
import org.store.clothstar.product.domain.ProductImage
import org.store.clothstar.product.domain.type.ImageType
import org.store.clothstar.product.dto.request.ProductCreateRequest
import org.store.clothstar.product.dto.response.ProductResponse

@Service
class ProductApplicationService(
Expand Down Expand Up @@ -41,7 +43,7 @@ class ProductApplicationService(
val mainImageUrl = "https://on.com2us.com/wp-content/uploads/2023/12/VxdEKDNZCp9hAW5TU5-3MZTePLGSdlYKzEZUyVLDB-Cyo950Ee19yaOL8ayxgJzEfMYfzfLcRYuwkmKEs2cg0w.webp"
product.imageList.add(ProductImage(mainImageUrl, mainImageUrl, ImageType.MAIN))

// 3. 서브 이미지 업로드 및 저장
// 3. 서브 이미지 업로드 및® 저장
subImages?.forEach { subImage ->
// val subImageUrl = s3Service.uploadFile(subImage)
val subImageUrl = "https://on.com2us.com/wp-content/uploads/2023/12/%EC%98%A4%EA%B5%AC%EC%99%80-%EB%B9%84%EB%B0%80%EC%9D%98%EC%88%B2-002.jpg"
Expand All @@ -61,4 +63,10 @@ class ProductApplicationService(
product.items.add(item)
}
}

@Transactional(readOnly = true)
fun getProductDetails(productId: Long): ProductResponse {

return productService.getProductDetails(productId)
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
package org.store.clothstar.product.service

import jakarta.persistence.EntityNotFoundException
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import org.store.clothstar.product.domain.Product
import org.store.clothstar.product.dto.response.ProductResponse
import org.store.clothstar.product.repository.ItemRepository
import org.store.clothstar.product.repository.OptionValueRepository
import org.store.clothstar.product.repository.ProductOptionRepository
import org.store.clothstar.product.repository.ProductRepository

@Service
class ProductService(
private val productRepository: ProductRepository,
) {
@Transactional
fun createProduct(product: Product): Product {
return productRepository.save(product)
}

@Transactional(readOnly = true)
fun getProductDetails(productId: Long): ProductResponse {
val product = productRepository.findWithDetailsByProductId(productId)
.orElseThrow { EntityNotFoundException("상품을 찾을 수 없습니다.") }

return ProductResponse.from(product)
}

fun findByProductIdIn(productIds: List<Long>): List<Product> {
return productRepository.findByProductIdIn(productIds).map {
it ?: throw IllegalArgumentException("상품을 조회할 수 없습니다.")
}
}

@Transactional
fun createProduct(product: Product): Product {
return productRepository.save(product)
}
}
6 changes: 4 additions & 2 deletions src/main/resources/application-db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ spring:
ddl-auto: create
properties:
hibernate:
default_batch_fetch_size: 1000
dialect: org.hibernate.dialect.MySQLDialect
format_sql: true

Expand All @@ -67,8 +68,9 @@ spring:
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQLDialect
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000
format_sql: true
show_sql: true

0 comments on commit 7ece7f7

Please sign in to comment.