У меня есть Пользователь, и у пользователя есть Расходы. В таблице расходов я хочу иметь идентификатор расхода, пользователя, совершившего расход, и сумму расхода. В пользовательской таблице мне нужен идентификатор пользователя, его имя пользователя, его текущий баланс и список всех расходов, которые он сделал.
Я хочу присоединиться к этим 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.
Вы смешиваете две вещи, которые должны быть не связаны между собой: 1. ваша модель сохраняемости (сущности JPA), 2. ваши входные данные HTTP API. Используйте разные классы для моделирования этих двух частей. И подумайте о том, что должен принимать ваш API для создания расхода: вам нужна сумма и что-то, что однозначно идентифицирует пользователя. Таким образом, объект JSON должен содержать сумму и идентификатор пользователя. Создайте класс DTO, который сопоставляется с этой структурой JSON. Затем используйте JPA, чтобы получить пользователя, идентифицированного по полученному идентификатору пользователя, и создайте расход, содержащий полученную сумму и найденного пользователя.
Попробуйте отправить почтовый запрос с этой полезной нагрузкой (поле «пользователь» начинается с маленькой буквы), и я думаю, что поля «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)
покажите метод, который получает объект User/Expense и все сохраняет. Просто чтобы быть более понятным здесь