spring JPA entities: cheat sheet

spring JPA entities: cheat sheet

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. 😅

Table of Contents

🌸 One to Many

🌷 One to One

🌼 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.

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, 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.

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

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

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.

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.

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=falsemeans the address_id can’t be null, or in other words each customer needs to have an address associated with it.
-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.

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.

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.

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

Leave a Reply

Your email address will not be published. Required fields are marked *