Filip Pranklin
Filip Pranklin

Reputation: 345

How to correctly join tables in Spring Boot

I have a User, and the user has Expenses. In expenses table I want to have the expense id, the user who made the expense and the amount of expense. In the user table I want user id, his username, his current balance and list of all expenses he's made.

I want to join those 2, but I don't know how to correctly reference user, therefore user form Expense class is always null.

First, I send post request to create a user:

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

then I want to create an expense, but here I'm not sure how to correctly send a User:

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

and this doesn't work, then I tried like this:

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

and that didn't work either.

This is the User class:

@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;
    ...

I removed getters and setters from here.

Here is the Expense class:

@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;
    ...

For saving the expense I use .save() from JpaRepository<Expense, Long> and for retrieveing everyting I use .findAll().

The result is always the same: get for all expenses gives

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

and get for all users gives

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

Now I'm not sure whether I'm sending the requests the wrong way or joining the tables the wrong way or both.

EDIT: here is 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(...) from ExpenseService

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

and 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);
    }
}

the rest of the User model is the same as the Expense model.

Upvotes: 1

Views: 2706

Answers (1)

Alexander Demchik
Alexander Demchik

Reputation: 49

Try to send post request with this payload (field 'user' begins with a small letter), and i think the 'id' field in user object should be enough

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

EDIT: Also you need to add @JsonIgnoreProperties("expenses") to your Expense entity to prevent jackson from recursive reading 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;
.....

Upvotes: 1

Related Questions