본문 바로가기

카테고리 없음

24.01.24 백오피스3

원투메니 이런거 없이 메뉴와 스토어 연결법

 

네, @OneToMany 관계를 설정하지 않고 Menu를 생성하려면 createMenu 메서드를 다음과 같이 변경할 수 있습니다.

기존의 Menu 생성자에서 store 프로퍼티를 제거하고, Store 엔티티를 참조하는 storeId를 menu 테이블에 추가해야 합니다. 그리고 MenuServiceImpl.createMenu 메서드에서 Store를 찾아 Menu를 생성할 때 storeId를 설정해줍니다.

kotlin
override fun createMenu(storeId: Long, menuCreateDto: MenuCreateDto): MenuDto {
    val newMenu = Menu(
        name = menuCreateDto.name,
        price = menuCreateDto.price,
        description = menuCreateDto.description,
        storeId = storeId
    )
    menuRepository.save(newMenu)
    return MenuDto.from(newMenu)
}
그런 다음 Menu 엔티티에서 Store를 참조하는 부분을 제거하고 storeId 필드를 추가합니다.

kotlin
@Entity
@Table(name = "menu")
class Menu(
    @Column(name = "name", nullable = false)
    var name: String,

    @Column(name = "description")
    var description: String,

    @Column(name = "price", nullable = false)
    var price: Int,

    @Column(name = "store_id", nullable = false)
    var storeId: Long
) : BaseTimeEntity() {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null
}
이렇게 하면 Store 엔티티에서 Menu 엔티티를 직접 참조하지 않아도 Menu를 생성하고 관리할 수 있습니다. 그러나 이렇게 하면 Store에서 해당 Menu를 찾을 수 없으므로, Menu를 찾을 때마다 storeId를 기반으로 검색해야 합니다. 이는 성능에 영향을 줄 수 있으므로, 이 점을 고려하여 설계해야 합니다.




그러니까 override fun getAllMenus(storeId: Long): List<MenuDto> {
val store = storeRepository.findByIdOrNull(storeId) ?: throw ModelNotFoundException("Store", storeId)
return store.menus.map { MenuDto.from(it) }
}에서 menus, it이 발개짐

suh75321의 어시스턴트
GPT-4
네, 맞습니다. Store 엔티티에서 Menu 엔티티의 참조를 제거하면 store.menus는 더 이상 유효하지 않습니다. 대신 Menu 엔티티에서 storeId를 사용하여 해당 Store에 속한 모든 메뉴를 찾아야 합니다.

따라서 getAllMenus 메서드를 다음과 같이 변경해야 합니다:

kotlin
override fun getAllMenus(storeId: Long): List<MenuDto> {
    val menus = menuRepository.findAllByStoreId(storeId)
    return menus.map { MenuDto.from(it) }
}
그리고 MenuRepository에 findAllByStoreId 메서드를 추가해야 합니다:

kotlin
interface MenuRepository : JpaRepository<Menu, Long> {
    fun findAllByStoreId(storeId: Long): List<Menu>
}
이렇게 하면 Menu 엔티티에서 storeId를 기반으로 해당 Store에 속한 모든 메뉴를 찾을 수 있습니다. 그러나 이 방식은 Store와 Menu 사이의 양방향 관계를 단방향으로 변경하므로, Store에서 직접 Menu를 찾을 수 없다는 점을 염두에 두어야 합니다. 이는 Store와 Menu 사이의 관계를 처리하는 방식에 따라 성능과 유지 관리에 영향을 줄 수 있습니다.

 

근데 store가 회색이니

override fun createMenu(storeId: Long, menuCreateDto: MenuCreateDto): MenuDto {
    val newMenu = Menu(
        name = menuCreateDto.name,
        price = menuCreateDto.price,
        description = menuCreateDto.description,
        storeId = storeId
    )
    menuRepository.save(newMenu)
    return MenuDto.from(newMenu)
}

이렇게 없애줌

 

어제나온 에러를 검색해 보니까

PostgreSQL "menu" 테이블 관련 에러 해결 방법 🛠️

여러분의 시스템에서 relation "menu" does not exist라는 에러가 발생했을 때 해결 방법을 간략하게 설명해 드리겠습니다. 이러한 에러 메시지는 PostgreSQL 데이터베이스에서 "menu"라는 이름의 테이블 또는 뷰(relation)를 찾을 수 없을 때 발생합니다. 해결을 위해 몇 가지 점검 사항과 조치를 살펴봅시다. 😊


에러 원인 파악 및 해결 방법

  • 테이블 이름 확인: 쿼리에서 사용하는 "menu" 테이블 이름이 데이터베이스에 실제 존재하는지 확인하세요. 오타가 없는지, 대소문자를 정확하게 맞추었는지 점검합니다.
  • 스키마 설정 확인: menu 테이블이 속한 스키마가 올바르게 설정되어 있는지 점검합니다. PostgreSQL은 대소문자를 구분하므로 테이블 생성 시 사용한 스키마와 동일한 스키마를 참조해야 합니다.
  • 테이블 생성: 테이블이 실제로 존재하지 않는다면, CREATE TABLE 문을 사용하여 menu 테이블을 생성해야 합니다. 데이터베이스 마이그레이션 도구(예: Liquibase)를 사용했다면, 마이그레이션이 정상적으로 수행되었는지 확인하세요. [1]
  • 사용자 권한 확인: 해당 테이블에 접근할 수 있는 권한이 부여되었는지 확인합니다. 권한이 없다면 요구되는 권한을 부여하거나, 적절한 사용자로 접속하세요.
  • 서버, 포트, 데이터베이스명 확인: 때때로 데이터베이스 연결 설정에서 서버 주소, 포트 번호, 또는 데이터베이스명이 잘못 설정되어 있는 경우에도 이러한 에러가 발생할 수 있습니다. 설정을 다시 확인하세요.

어려운 점이 있으시면 추가적인 질문을 주시면 됩니다. 데이터베이스 관련 문제는 복잡할 수 있으니 천천히 한 단계씩 해결해 나가는 것이 좋습니다. 화이팅입니다!

 

라고 했는데 어떤 방법으로도 안되서 튜터님에게 가봤는데도 안될 이유가 없다는 답만 나오다가, menu 테이블에 스페이스가 있다는걸 튜터님께서 발견하셨다. 

menu가 아니라 menu 이였던것이다. 그러니 menu를 찾지를 못한것이다. 어제 새벽까지 몰두했던게 스페이스 때문이라닠ㅋㅋㅋ 이 무슨!!!     

 

@PreAuthorize("hasRole('ADMIN') or hasRole('STORE')")

이건 어드민이랑 스토어여야만 생성이 가능하게 해준다.

 

@AuthenticationPrincipal user: UserPrincipal,이것과  @PreAuthorize을 사용해서 store만이 메뉴를 생성,수정,삭제하게 하려면

package com.sparta.dianomi.domain.store.model.controller


import com.sparta.dianomi.authority.security.UserPrincipal
import com.sparta.dianomi.domain.store.model.dto.MenuCreateDto
import com.sparta.dianomi.domain.store.model.dto.MenuDto
import com.sparta.dianomi.domain.store.model.dto.MenuUpdateDto
import com.sparta.dianomi.domain.store.model.service.MenuService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*

@RestController
@Tag(name = "Menu", description = "메뉴 API")
@RequestMapping("/stores/{storeId}/menus")
class MenuController(private val menuService: MenuService) {

    @Operation(summary = "메뉴 정보 업데이트", description = "주어진 메뉴 ID에 해당하는 메뉴의 정보를 업데이트합니다.")
    @PreAuthorize("hasRole('STORE')")
    @PutMapping("/{menuId}", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun updateMenu(
        @AuthenticationPrincipal user: UserPrincipal,
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "메뉴 ID") @PathVariable menuId: Long,
        @Parameter(description = "업데이트할 메뉴 정보") @RequestBody menuUpdateDTO: MenuUpdateDto
    ): ResponseEntity<out Any> {
        menuService.updateMenu(storeId, menuId, menuUpdateDTO)
        return ResponseEntity.ok(menuUpdateDTO)
    }

    @Operation(summary = "새 메뉴 생성", description = "새로운 메뉴를 생성합니다.")
    @PreAuthorize("hasRole('STORE')")
    @PostMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
    fun createMenu(
        @AuthenticationPrincipal user: UserPrincipal,
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "생성할 메뉴 정보") @RequestBody menuCreateDto: MenuCreateDto
    ): ResponseEntity<MenuDto> {
        val menuDto = menuService.createMenu(storeId, menuCreateDto)
        return ResponseEntity.status(HttpStatus.CREATED).body(menuDto)
    }

    @Operation(summary = "메뉴 삭제", description = "주어진 메뉴 ID에 해당하는 메뉴를 삭제합니다.")
    @PreAuthorize("hasRole('STORE')")
    @DeleteMapping("/{menuId}", produces = [MediaType.TEXT_PLAIN_VALUE])
    fun deleteMenu(
        @AuthenticationPrincipal user: UserPrincipal,
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "메뉴 ID") @PathVariable menuId: Long
    ): ResponseEntity<out Any> {
        menuService.deleteMenu(storeId, menuId)
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
    }

    @GetMapping("/{menuId}", produces = [MediaType.APPLICATION_JSON_VALUE])
    @Operation(summary = "특정 메뉴 조회", description = "주어진 메뉴 ID에 해당하는 메뉴를 조회합니다.")
    fun getMenu(
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "메뉴 ID") @PathVariable menuId: Long
    ): ResponseEntity<out Any> {
        val menu = menuService.getMenu(storeId, menuId)
        return ResponseEntity.ok(menu)
    }

    @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
    @Operation(summary = "모든 메뉴 조회", description = "모든 메뉴를 조회합니다.")
    fun getMenus(
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long
    ): ResponseEntity<out Any> {
        val menus = menuService.getAllMenus(storeId)
        return if (menus.isNotEmpty()) {
            ResponseEntity.ok(menus)
        } else {
            ResponseEntity.status(HttpStatus.NOT_FOUND).body(mapOf("message" to "메뉴가 존재하지 않습니다."))
        }
    }
}

 

 

  • 카트 (성욱)
    • Cart DB생성 (ERD참고해서 그대로 만들기)
    • Cart 에 담기, 삭제, 전부삭제 기능 만들어주세요. (이때 전부삭제는 로그인한 회원의 카트만 삭제되어야 합니다.)
  • 카테고리(성욱)
    • Store 테이블에 Category 속성 추가.
    • Enum class로 Category 작성.
    • Store controller에 Category 목록, 카테고리별 조회 추가.

이걸 해야하는데.. 우선 데이터베이스부터 만들고, 카테고리는 store의 하위니까 그 안에 category를 만들고 문자니까 varchar로 만들고 cart는 테이블 째로 새로 만든다. 들어가야할건 카트의 id, member_id, store_id, menu_id, 수량(count)를 만들고 다 숫자니까 int로 만들었다. 그럼 이제 작업하면 된다.

 

그런데, @PreAuthorize말고 다른걸 쓰라고 하셔서 변경했는데,

store에 userid를 추가하고, service, serviceimpl, controller을 전부 변경했다.

 

@RestController
@Tag(name = "Menu", description = "메뉴 API")
@RequestMapping("/stores/{storeId}/menus")
class MenuController(private val menuService: MenuService) {

    @Operation(summary = "메뉴 정보 업데이트", description = "주어진 메뉴 ID에 해당하는 메뉴의 정보를 업데이트합니다.")
    @PutMapping("/{menuId}", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun updateMenu(
        @AuthenticationPrincipal user: UserPrincipal,
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "메뉴 ID") @PathVariable menuId: Long,
        @Parameter(description = "업데이트할 메뉴 정보") @RequestBody menuUpdateDTO: MenuUpdateDto
    ): ResponseEntity<out Any> {
        menuService.updateMenu(storeId, menuId, menuUpdateDTO, user.id)
        return ResponseEntity.ok(menuUpdateDTO)
    }

    @Operation(summary = "새 메뉴 생성", description = "새로운 메뉴를 생성합니다.")
    @PostMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
    fun createMenu(
        @AuthenticationPrincipal user: UserPrincipal,
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "생성할 메뉴 정보") @RequestBody menuCreateDto: MenuCreateDto
    ): ResponseEntity<MenuDto> {
        val menuDto = menuService.createMenu(storeId, menuCreateDto, user.id)
        return ResponseEntity.status(HttpStatus.CREATED).body(menuDto)
    }

    @Operation(summary = "메뉴 삭제", description = "주어진 메뉴 ID에 해당하는 메뉴를 삭제합니다.")
    @DeleteMapping("/{menuId}", produces = [MediaType.TEXT_PLAIN_VALUE])
    fun deleteMenu(
        @AuthenticationPrincipal user: UserPrincipal,
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "메뉴 ID") @PathVariable menuId: Long
    ): ResponseEntity<out Any> {
        menuService.deleteMenu(storeId, menuId,user.id)
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
    }

    @GetMapping("/{menuId}", produces = [MediaType.APPLICATION_JSON_VALUE])
    @Operation(summary = "특정 메뉴 조회", description = "주어진 메뉴 ID에 해당하는 메뉴를 조회합니다.")
    fun getMenu(
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long,
        @Parameter(description = "메뉴 ID") @PathVariable menuId: Long
    ): ResponseEntity<out Any> {
        val menu = menuService.getMenu(storeId, menuId)
        return ResponseEntity.ok(menu)
    }

    @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
    @Operation(summary = "모든 메뉴 조회", description = "모든 메뉴를 조회합니다.")
    fun getMenus(
        @Parameter(description = "스토어 ID") @PathVariable storeId: Long
    ): ResponseEntity<out Any> {
        val menus = menuService.getAllMenus(storeId)
        return if (menus.isNotEmpty()) {
            ResponseEntity.ok(menus)
        } else {
            ResponseEntity.status(HttpStatus.NOT_FOUND).body(mapOf("message" to "메뉴가 존재하지 않습니다."))
        }
    }
}

 

컨트롤러엔 이렇게 삭제, 수정, 생성에 (storeId, menuCreateDto, user.id)이렇게 뒤에 userid를 넣고 

interface MenuService {
    fun createMenu(storeId: Long, menuCreateDto: MenuCreateDto, userId: Long): MenuDto
    fun updateMenu(storeId: Long, id: Long, menuUpdateDto: MenuUpdateDto, userId: Long): MenuDto
    fun getMenu(storeId: Long, id: Long): MenuDto
    fun getAllMenus(storeId: Long): List<MenuDto>
    fun deleteMenu(storeId: Long, id: Long, userId: Long)
}

서비스에도 이렇게 뒤에 userId: Long를 넣고 

 

@Service
class MenuServiceImpl(
    private val storeRepository: StoreRepository,
    private val menuRepository: MenuRepository
): MenuService {

    override fun getAllMenus(storeId: Long): List<MenuDto> {
        val menus = menuRepository.findAllByStoreId(storeId)
        return menus.map { MenuDto.from(it) }
    }

    override fun getMenu(storeId: Long, id: Long): MenuDto {
        val menu = menuRepository.findByIdOrNull(id) ?: throw ModelNotFoundException("Menu", id)
        return MenuDto.from(menu)
    }

    override fun createMenu(storeId: Long, menuCreateDto: MenuCreateDto, userId: Long): MenuDto {
        _checkOwner(storeId, userId)

        val newMenu = Menu(
            name = menuCreateDto.name,
            price = menuCreateDto.price,
            description = menuCreateDto.description,
            storeId = storeId
        )
        menuRepository.save(newMenu)
        return MenuDto.from(newMenu)
    }

    override fun deleteMenu(storeId: Long, id: Long, userId: Long) {
        _checkOwner(storeId, userId)
        val menu = menuRepository.findByIdOrNull(id) ?: throw ModelNotFoundException("Menu", id)
        menuRepository.delete(menu)
    }

    override fun updateMenu(storeId: Long, id: Long, menuUpdateDto: MenuUpdateDto, userId: Long): MenuDto {
        _checkOwner(storeId, userId)
        val menu = menuRepository.findByIdOrNull(id) ?: throw ModelNotFoundException("Menu", id)
        menu.name = menuUpdateDto.name
        menu.price = menuUpdateDto.price
        menu.description = menuUpdateDto.description
        menuRepository.save(menu)
        return MenuDto.from(menu)
    }

    private fun _checkOwner(storeId: Long,userId: Long) {
        val store : Store =storeRepository.findByIdOrNull(storeId) ?: throw Exception();
        if (store.userId != userId) {
                throw Exception()
            }


    }

}

              

서비스임플에는 

 private fun _checkOwner(storeId: Long,userId: Long) {
        val store : Store =storeRepository.findByIdOrNull(storeId) ?: throw Exception();
        if (store.userId != userId) {
                throw Exception()
            }


    }

이걸 추가하고 역시 생성수정삭제에 각각

 

(storeId: Long, menuCreateDto: MenuCreateDto, userId: Long): MenuDto {
        _checkOwner(storeId, userId)

이렇게 유저아이디에 체크오너를 넣어서 해결했다. 근데 오류가뜬다고??? 아............... 오늘도 힘들겠네.. 그 후에 일어난 일은 다음 til에 적기로 해야겠다.

 

참고로 git pull origin 하기전에 git fetch