Как правильно соединять таблицы в Spring Boot

У меня есть Пользователь, и у пользователя есть Расходы. В таблице расходов я хочу иметь идентификатор расхода, пользователя, совершившего расход, и сумму расхода. В пользовательской таблице мне нужен идентификатор пользователя, его имя пользователя, его текущий баланс и список всех расходов, которые он сделал.

Я хочу присоединиться к этим 2, но я не знаю, как правильно ссылаться на пользователя, поэтому класс Expense формы пользователя всегда равен нулю.

Сначала я отправляю почтовый запрос для создания пользователя:

{
    "username":"abcd",
    "balance":"100"
}

то хочу создать расход, а тут не уверен как правильно отправить юзера:

{
    "username":"abcd",
    "id":"1",
    "balance":"100",
    "amount":"20"
}

и это не работает, тогда я пробовал так:

{
    "User":{
    "username":"abcd",
    "id":"1",
    "balance":"100"
    },
    "amount":"20"
}

и это тоже не сработало.

Это класс пользователя:

@Entity
@Table(name = "Users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @NotBlank(message = "Username is mandatory")
    private String username;
    private Double balance = 0.0;

    @OneToMany(mappedBy = "user")
    private List<Expense> expenses;
    ...

Я удалил геттеры и сеттеры отсюда.

Вот класс расходов:

@Entity
@Table(name = "Expenses")
public class Expense {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    private Double amount;
    ...

Для экономии средств я использую .save() из JpaRepository<Expense, Long>, а для извлечения всего использую .findAll().

Результат всегда один: получить за все расходы дает

{
        "id": 1,
        "user": null,
        "amount": 20
}

и получить для всех пользователей дает

{
        "id": 1,
        "username": "abcd",
        "balance": 100,
        "expenses": []
}

Теперь я не уверен, отправляю ли я запросы неправильно, или неправильно присоединяюсь к таблицам, или и то, и другое.

Обновлено: вот ExpenseController:

@RestController
public class ExpenseController {

    @Autowired
    IExpenseService expenseService;

    @GetMapping("/expenses")
    public List<Expense> findExpenses() {
        return expenseService.findAll();
    }

    @PostMapping("/expenses")
    public void createNewExpense(@RequestBody Expense expense) {
        expenseService.createNewExpense(expense);
    }
}

createNewUser(...) из ExpenseService

@Override
    public void createNewExpense(Expense expense) {
        repository.save(expense);
    }

и ExpenseRepository:

@Repository
public interface ExpenseRepository extends JpaRepository<Expense, Long> {
}

UserController:

@RestController
public class UserController {

    @Autowired
    IUserService userService;

    @GetMapping("/users")
    public List<User> findUsers() {
        return userService.findAll();
    }

    @GetMapping("/users/{id}")
    public User findUserById(@PathVariable Long id) {
        return userService.findById(id);
    }

    @PostMapping("/users")
    public ResponseEntity<Object> createUser(@RequestBody User user) {

        if (userService.checkIfUsernameIsTaken(user)) {

            Map<String, Object> response = new HashMap<>();
            response.put("status", HttpStatus.NOT_ACCEPTABLE);
            response.put("errors", "Username is already taken");
            response.put("timestamp", new Date());

            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
        } else {
            userService.createNewUser(user);
            User currentUser = userService.findById(userService.findByUsername(user.getUsername()));
            Map<String, Object> response = new HashMap<>();
            response.put("id", currentUser.getId());
            response.put("username", currentUser.getUsername());
            response.put("balance", currentUser.getBalance());
            response.put("expenses", currentUser.getExpenses());
            return new ResponseEntity<>(response, HttpStatus.OK);
        }
    }

    @DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }

    @PutMapping("/users/{id}/{balance}")
    public void updateBalance(@PathVariable Long id, @PathVariable Double balance) {
        userService.updateBalance(id, balance);
    }
}

остальная часть модели User такая же, как и модель Expense.

покажите метод, который получает объект User/Expense и все сохраняет. Просто чтобы быть более понятным здесь

Maciej Kowalski 30.05.2019 14:59

Вы смешиваете две вещи, которые должны быть не связаны между собой: 1. ваша модель сохраняемости (сущности JPA), 2. ваши входные данные HTTP API. Используйте разные классы для моделирования этих двух частей. И подумайте о том, что должен принимать ваш API для создания расхода: вам нужна сумма и что-то, что однозначно идентифицирует пользователя. Таким образом, объект JSON должен содержать сумму и идентификатор пользователя. Создайте класс DTO, который сопоставляется с этой структурой JSON. Затем используйте JPA, чтобы получить пользователя, идентифицированного по полученному идентификатору пользователя, и создайте расход, содержащий полученную сумму и найденного пользователя.

JB Nizet 30.05.2019 15:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
949
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуйте отправить почтовый запрос с этой полезной нагрузкой (поле «пользователь» начинается с маленькой буквы), и я думаю, что поля «id» в пользовательском объекте должно быть достаточно.

  { 
   "user":{
     "username":"abcd",
     "id":"1",
     "balance":"100"
    },
    "amount":"20"
  }

Обновлено: Также вам нужно добавить @JsonIgnoreProperties("расходы") к вашему объекту Expense, чтобы предотвратить рекурсивное чтение Джексоном json

@Entity
@Table(name = "Expenses")
public class Expense {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @JsonIgnoreProperties("expenses")
  @ManyToOne
  @JoinColumn(name = "user_id")
  private User user;

  private Double amount;
.....

ад вырывается на свободу, когда я пробую это с «пользователем». Я получаю Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)

Filip Pranklin 30.05.2019 15:25

Другие вопросы по теме