spring JPA entities: cheat sheet

Ashley D - Apr 28 - - Dev Community

Week 10 of coding bootcamp was marked by lots of junk food, a sleepless Sunday night from drinking my coffee too late, and oh yeah learning about Spring JPA entities. Learning them felt like mental gymnastics for me. Now that my brain has shakily landed from its endless flips and rumination, I wanted to share my basic understanding of the how behind each. 😅

Flipping For Code

Table of Contents

  1. 🌸 One to Many

  2. 🌷 One to One

  3. 🌼 Many to Many


One to Many

Below we have two entities from the basic backend framework of an ecommerce’s app’s backend. A customer has a one to many relationship with orders. In other words, a customer can buy multiple orders.

One to Many Code

Order Customer
@ManyToOne annotation above private Customer customer indicates many Order instances can belong to one Customer instance. @OneToMany annotation above private List<Order> orders means that one Customer instance can have multiple Order instances.
@JoinColumn indicates the foreign key of customer_id references the one in Customer table. nullable=false means that each order must have a customer instance (when being created/updated), or in other words belong to a customer. mappedBy= "customer" (written above List<Order> orders) means that the list of orders is mapped to each customer via the Customer customer field on the Orders table (aka… the Orders table owns the relationship).
Hibernate then fetches the associated Customer instance based on that foreign key. This is how the order(s) are associated with the respective customer. Remember how in the Order table, Hibernate gets the Customer instance via the foreign key and that is how it’s associated with those orders?

*Note: Even though we don’t see a list of orders or the customer object - we can access that relationship programmatically via JPA, as discussed above.

Here are how the Order and Customer Table look respectively on Postgres.

One to Many Tables

@JsonIgnore

The @JsonIgnore is to prevent “looping” (aka circular dependency) when we display the data. For example, when we made a request to get orders tied to a customer, without JSON, it would show the order, then the customer, which then shows the customer field info and then calls the order field which then calls the customer, ad infinitum.

With @JsonIgnore- only the Order information would show when making that get request.
When deciding where to place @JsonIgnore- you can think of it as we don’t need to show the customer when querying orders, but it would be more helpful to show the orders when querying a customer. An alternative way of approaching that thought process is: we put @JsonIgnore over the customer field, because a customer can exist without orders but not vice versa.

Owning Table

Orders is the owning table, which means any changes to the relationship, such as adding or removing orders for a customer, should be made through the customer field in the Order entity.) The owning side is also where the relationship is persisted in the database; that is the owning side is the one that owns the foreign key in the relationship.

We chose orders as the owning side, because of operational logic. You typically create orders and associate with customers, not the other way around.

The example code block shows how we add an order to a customer. order represents the new order we are adding (sent via the request body in the API call @PostMapping("/customers/{id}/orders")).



 public Customer addOrderToCustomer(Integer id, Order order) throws Exception {
        Customer customer = customerRepository.findById(id).orElseThrow(() -> new Exception("Customer not found"));
        order.setCustomer(customer);
        orderRepository.save(order);
        return customer;
    }


Enter fullscreen mode Exit fullscreen mode

Notice how the updates in the code are done via the order’s side. The customer that has made the order is set to the customer field in the Order entity. This Order instance that now has the respective customer tied to it is then saved (persisted) to the Order database.

Bidirectional

As a side note, as cascade persist is not used above, you can set the order on the customer side as well to allow for bidirectional navigation. Cascade persist means that operations on say a customer entity would persist to the associated order entities (i.e. if you deleted a customer, then the associated customer would aso delete) …however, in our case- as we are primarily accessing orders from the orders side (i.e. we typically retrieve orders and associate them with customers), then we don’t need that bidirectional code added.

Note:

Keep in mind, for unidirectional one-to-many relationships, in most cases- you have to set both entities "to each other", in other words for example set a team to a player, and player to a team. On the other hand, many-to-many relationships are bidirectional and that means if you set a post to have a tag, you don't need to set the post to the tag- since JPA handles that on the backend (made possible by the associations between both entities via the join table).


One to One

Below we see how each record in our Address entity is associated with one record in the Customer entity., and vice versa. That is, in our hypothetical scenario: each customer can have one address, and one address can belong to one customer.

One to One Code

All the annotations are handled on the Customer entity side:

  • @OneToOne annotation in our Customer entity above private Address address means that one customer instance has one address
  • CascadeType.ALL in our Customer entity means that for any actions taken on a Customer instance, should also be applied to the Address. For example, if a customer is deleted, then its respective address will also be deleted. Likewise, if a customer is created, then it will be expected to have an address added.
  • @JoinColumn annotation indicates the address_id is the foreign key.
  • nullable=false means the address_id can’t be null, or in other words each customer needs to have an address associated with it. When creating or updating a user, it must have an address. -unique=true means that for each customer, they must have a unique address.

This screenshot below shows the Customer table in Postgres, and you can see the foreign key address_id as the first column- essentially allowing this table to “link” to the Address table.

One to One Table

Many to Many

Below we see a relationship where one order can have multiple products (i.e. TV, sofa, etc), and one product can have many orders. In other words, orders and products have a many to many relationship.

Many to Many Code

Order Product
@ManyToMany above List<Product> means one order can have multiple products. @ManyToMany above List<Order> means one product can have multiple orders.
@JoinTableis on the Order side. We query most from Orders and when we query, it allows the related products to properly display. @JsonIgnore is placed over List<Order> to prevent that circular reference data loop. Also, we don't need a product result to show affiliated orders, whereas we want to show orders with their affiliated products.
inverseJoinColumn means the foreign key of product_id references the one in the Product table (opposite side table). Hibernate then fetches the associated Product instance based on that foreign key. This is how the orders(s) are associated with the respective products. mappedBy = “products” means Orders is the owning side; you create Orders and associate products to orders. The list of orders is mapped to products via the Order table (through its join table). Remember how in the Order table, Hibernate gets the Product instance via the foreign key and that is how it’s associated with those orders?

*This below screenshot shows the Join Table that is actually created in Postgres from that @JoinTable annotation.

Many to Many Diagram Table

Side note:

  • On the Order table: =new ArrayList<> initializes the list of products with an empty array list, so that as products are added to an order, that array list is updated. This ensures that the products field is not null even if no products are associated with that order at initialization time.
  • On the Product table: =new ArrayList<> initializes the list of orders with an empty array list, so that as new orders are made with that product, then those orders can be added in. This ensures that the orders field is not null even if no orders are associated with that product at initialization time.
. . . . . . . . . . . .
Terabox Video Player