@NyxDeveloper

Как создавать связанные сущности через RESTController?

У меня есть две модели:
package com.example.timestat.models

import jakarta.persistence.*

@Entity
@Table(name = "subjects")
data class Subject(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    val id: Long,

    @Column(name = "name")
    val name: String,

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

    @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "subject")
    val events: List<Event> = listOf()
)

package com.example.timestat.models

import jakarta.persistence.*
import java.util.*

@Entity
@Table(name = "events")
data class Event(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    val id: Long,

    @Column(name = "name")
    val name: String,

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

    @Column(name = "date")
    val date: Date,

    @Column(name = "approved")
    val approved: Boolean,

    @Column(name = "type")
    val type: String,

    @Column(name = "sourceHref")
    val sourceHref: String,

    @ManyToOne(cascade = [CascadeType.REFRESH], fetch = FetchType.EAGER)
    val subject: Subject
)


И есть два контроллера:
package com.example.timestat.controllers

import com.example.timestat.models.Subject
import com.example.timestat.service.SubjectService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/subjects")
class SubjectController(@Autowired private val service: SubjectService) {
    @GetMapping("")
    fun getList(): List<Subject> {
        return service.list()
    }

    @GetMapping("/{id}")
    fun getById(@PathVariable id: Long): ResponseEntity<Subject> {
        val subject = service.oneById(id) ?: return ResponseEntity(HttpStatus.NOT_FOUND)
        return ResponseEntity(subject, HttpStatus.OK)
    }

    @PostMapping("")
    fun create(@RequestBody subject: Subject): ResponseEntity<Subject> {
        val created = service.save(subject)
        return ResponseEntity(created, HttpStatus.OK)
    }

    @DeleteMapping("/{id}")
    fun delete(@PathVariable id: Long): ResponseEntity<Subject> {
        service.existsById(id) ?: return ResponseEntity(HttpStatus.NOT_FOUND)
        service.delete(id)
        return ResponseEntity(HttpStatus.NO_CONTENT)
    }
}

package com.example.timestat.controllers

import com.example.timestat.models.Event
import com.example.timestat.service.EventService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/events")
class EventController(@Autowired private val service: EventService) {
    @GetMapping("")
    fun getList(): List<Event> {
        return service.list()
    }

    @GetMapping("/{id}")
    fun getById(@PathVariable id: Long): ResponseEntity<Event> {
        val event = service.oneById(id) ?: return ResponseEntity(HttpStatus.NOT_FOUND)
        return ResponseEntity(event, HttpStatus.OK)
    }

    @PostMapping("")
    fun create(@RequestBody subject: Event): ResponseEntity<Event> {
        val created = service.save(subject)
        return ResponseEntity(created, HttpStatus.OK)
    }

    @DeleteMapping("/{id}")
    fun delete(@PathVariable id: Long): ResponseEntity<Event> {
        service.existsById(id) ?: return ResponseEntity(HttpStatus.NOT_FOUND)
        service.delete(id)
        return ResponseEntity(HttpStatus.NO_CONTENT)
    }
}


Я отправляю запрос на создание нового Event:
{
    "name": "test event",
    "description": "test event",
    "date": "2023-07-22 17:57:42",
    "approved": false,
    "type": "speak",
    "sourceHref": "https://google.com",
    "subject": 1
}


На что получаю следующую ошибку:
JSON parse error: Cannot construct instance of `com.example.timestat.models.Subject` (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)


Следуя документации, hibernate должен сам подтягивать по id нужный Subject из бд, но он этого не делает, ругаясь на отсутствие конструктора.

Что я делаю не правильно?
  • Вопрос задан
  • 128 просмотров
Пригласить эксперта
Ответы на вопрос 2
xez
@xez
TL Junior Roo
Это ответ от контроллера. До гибернейта дело еще не дошлою
Ответ написан
azerphoenix
@azerphoenix Куратор тега Spring
Java Software Engineer
Добрый день.
Начнем с самого начала.
Рекомендую в первую очередь реализовать энтерпрайз паттерн DTO.
Создаете модель, которая описывает вашу сущность и которую планируете получать в контроллере для создания и отправлять ответ после создания.
Желательно называть их понятным образом.
Допустим, у вас есть entity - Event. EventCreationRequest, EventCreationResponse, EventDTO, EventUpdatingRequest и т.д.
Конечно же, вам нужно маппить данные между Event -- EventDTO.
Можно по-разному реализовать.
Есть готовые либы - MapStruct, ModelMapper, JMapper и др. Я предпочитаю использовать MapStruct или ModelMapper.
https://www.baeldung.com/spring-type-conversions
Если не хотите либу, то используйте интерфейс Converter<S,T> содержащийся в Spring. Он принимает 2 дженерика - source & target.
Например,
EventDTOMapper implements Converter<EventDTO, Event>

Отмечу, что для nested classes тоже стоит создать DTO.

Далее касаемо @PostMapping("") можно не указывать скобки, а просто @PostMapping.

return ResponseEntity(created, HttpStatus.OK)
можно более сокращенно ResponseEntity.ok(created);

Следуя документации, hibernate должен сам подтягивать по id нужный Subject из бд, но он этого не делает, ругаясь на отсутствие конструктора.

Отмечу, что каждая сущность должна иметь коструктор без аргументов.
В kotlin для spring есть соответствующие плагины, которые нужно подключать. (читаем документацию)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы