@NyxDeveloper

Какая практика считается лучшей при создании CRUD API со связанными таблицами?

Добрый день!
Начал вникать в котлин, чтобы было не скучно решил сделать простой туду-лист на Spring и столкнулся с неочевидной для меня проблемой передачи id связанных объектов в RestController.
Есть две модели:
@Entity
@Table(name = "lists")
data class List(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long,

    @Column(name = "name", nullable = false, unique = true)
    val name: String
)

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

    @Column(name = "name", nullable = false, unique = true)
    val name: String
    
    @ManyToOne
    @JoinColumn(name = "list_id")
    @field:NotNull
    val list: List,
)

Их репозитории идентичны:
interface ListRepository: CrudRepository<List, Long>

interface TodoRepository: CrudRepository<Todo, Long>


И их контроллеры:
@RestController
@RequestMapping("/api/lists")
class ListController(@Autowired private val listRepository: ListRepository) {
    @GetMapping("")
    fun getAllLists(): List<List> {
        return listRepository.findAll().toList()
    }

    @GetMapping("/{id}")
    fun getListById(@PathVariable("id") listId: Long): ResponseEntity<List> {
        val list = listRepository.findById(listId).orElse(null)
        return if (list != null) ResponseEntity(list, HttpStatus.OK)
        else ResponseEntity(HttpStatus.NOT_FOUND)
    }

    @PostMapping("")
    fun createList(@RequestBody list: List): ResponseEntity<List> {
        val createdList = listRepository.save(list)
        return ResponseEntity(createdList, HttpStatus.CREATED)
    }

    @PutMapping("/{id}")
    fun updateListById(@PathVariable("id") listId: Long, @RequestBody list: List): ResponseEntity<List> {
        val existingList = listRepository.findById(listId).orElse(null)

        if (existingList == null) {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }

        val updatedList = existingList.copy(
            name = list.name,
            description = list.description
        )
        listRepository.save(updatedList)
        return ResponseEntity(updatedList, HttpStatus.OK)
    }

    @DeleteMapping("/{id}")
    fun deleteListById(@PathVariable("id") listId: Long): ResponseEntity<List> {
        if (!listRepository.existsById(listId)) {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }
        listRepository.deleteById(listId)
        return ResponseEntity(HttpStatus.NO_CONTENT)
    }
}

@RestController
@RequestMapping("/api/todos")
class TodoController(@Autowired private val todoRepository: TodoRepository) {
    @GetMapping("")
    fun getAllTodos(): Todo<Todo> {
        return todoRepository.findAll().toTodo()
    }

    @GetMapping("/{id}")
    fun getTodoById(@PathVariable("id") todoId: Long): ResponseEntity<Todo> {
        val todo = todoRepository.findById(todoId).orElse(null)
        return if (todo != null) ResponseEntity(todo, HttpStatus.OK)
        else ResponseEntity(HttpStatus.NOT_FOUND)
    }

    @PostMapping("")
    fun createTodo(@RequestBody todo: Todo): ResponseEntity<Todo> {
        val createdTodo = todoRepository.save(todo)
        return ResponseEntity(createdTodo, HttpStatus.CREATED)
    }

    @PutMapping("/{id}")
    fun updateTodoById(@PathVariable("id") todoId: Long, @RequestBody todo: Todo): ResponseEntity<Todo> {
        val existingTodo = todoRepository.findById(todoId).orElse(null)

        if (existingTodo == null) {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }

        val updatedTodo = existingTodo.copy(
            name = todo.name,
            description = todo.description
        )
        todoRepository.save(updatedTodo)
        return ResponseEntity(updatedTodo, HttpStatus.OK)
    }

    @DeleteMapping("/{id}")
    fun deleteTodoById(@PathVariable("id") todoId: Long): ResponseEntity<Todo> {
        if (!todoRepository.existsById(todoId)) {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }
        todoRepository.deleteById(todoId)
        return ResponseEntity(HttpStatus.NO_CONTENT)
    }
}

Я полагал, что запрос на создание Todo подобного типа автоматически обрабатывается классом Entity и сериализация пройдет под капотом у джавы:
{
    "name": "test",
    "list": 1,
}

Но он вызывает ошибку отсутствия конструктора у класса List.
Я так понимаю, что аннотациями здесь не обойтись, да и контролировать сериализацию полей в json хотелось бы самому.
В интернете много статей этому посвящено, но большинство из них для джавы и не то что бы свежие. Есть какие-либо лаконичные проверенные современные практики на подобии DTO в этой области? Если есть, приведите пожалуйста ваши примеры их использования или доки, в которых они описаны, буду крайне благодарен!
  • Вопрос задан
  • 81 просмотр
Пригласить эксперта
Ваш ответ на вопрос

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

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