Simplifying Database Management with Spring Data JPA

Table of Contents

Streamlining Database Schema Creation with Spring Data JPA

Spring Data JPA, which uses Hibernate as the ORM, has abstracted and provided a lot of convenience when interacting with databases. With Spring Data JPA, it has eliminated the need to manually create tables in the database. By marking a Java class with @Entity, Spring Data JPA automatically helps us create the table when we run the Spring Boot application. This behavior is controlled by thespring.jpa.hibernate.ddl-auto property in theapplication.properties file. Below are a few configuration options that we can set:

  • validate: Hibernate only validates that the database schema matches the entities. If there's a mismatch, the application fails to start.
  • update: Hibernate updates the database schema to match the entity definitions (adding tables/columns but won’t delete anything).
  • create: Hibernate drops the existing schema and creates it anew every time you start the application.

Customizing Table Names

By default, the table name will be the lowercase version of the class name. However, we can choose to manually set the name of the table using the @Table annotation. Here we have an example of a entity class in spring boot.

1@Entity
2@Data
3@Table(name = "users")
4public class User {
5    @Id
6    @GeneratedValue(strategy = GenerationType.IDENTITY)
7    private Long id;
8
9    private String username;
10    private String password;
11
12    @OneToOne
13    @JoinColumn(name = "partner_id")
14    private User partner;
15
16    @CreationTimestamp
17    @Column(name = "date_created", nullable = false, updatable = false)
18    private LocalDateTime dateCreated;
19}

In this example, we define a simple User class with fields such as id, username, password, date_created. Additionally, the class contains a foreign key partner_id which references another user in the database. This is established using the @JoinColumn and @OneToOne. These annotations inform spring boot that each user is associated with exactly one partner partner, and that a column called partner_id should be created in the database to represent this relationship. Consequntly, when running the application, the following SQL code will be generated and executed by Hibernate to create or update the database schema in the database.

1create table
2  public.users (
3    id bigserial not null,
4    password character varying(255) null,
5    username character varying(255) null,
6    partner_id bigint null,
7    date_created timestamp without time zone not null,
8    constraint users_pkey primary key (id),
9    constraint uk_sr3rf8kjb54t8ryfh9tk2156k unique (partner_id),
10    constraint fkeenhxwkw7u34s1uivix91ipb8 foreign key (partner_id) references users (id)
11  ) tablespace pg_default;

Understanding One-to-Many annotation

In a traditional SQL database, a one-to-many relationship is typically represented using joins. For example, consider the case of a customer placing multiple orders. While a customer may have many orders, each order belongs to only one customer. To map this relationship, the Order table would include a foreign key column, customerId, referencing the id in the Customer table. To retrieve all the orders placed by a customer, we could perform a join between the Customer and Order tables onCustomer.id =Order.customerId. Spring Boot simplifies this process by eliminating the need for manual joins and allowing us to manage such relationships in a more object-oriented way.

Using the example of a customer having many orders, we can represent this one-to-many relationship in spring boot with the following entity classes

1@Entity
2@Getter
3@Setter
4@AllArgsConstructor
5@NoArgsConstructor
6public class Customer {
7    @Id
8    @GeneratedValue(strategy = GenerationType.IDENTITY)
9    public Long customerId;
10
11    private String name;
12    private String address;
13    private String password;
14
15
16    @OneToMany(mappedBy = "customerId", fetch = FetchType.EAGER)
17    @JsonManagedReference
18    private Set<Order> order = new HashSet<>();
19
20}

In the Customer class, we use the @OneToMany annotation, mapped by the customerId field in the Order entity. This mapping allows us to access a customer's orders through Customer.order, with Spring Boot automatically performing a join using the customer's primary key to retrieve all related orders. By default, we can use FetchType.EAGER, which loads the orders along with the rest of the Customer fields. Alternatively, setting FetchType.LAZY defers the loading of orders until the getOrders() method is explicitly called, allowing on-demand retrieval.

1@Entity
2@Data
3@Table(name="orders")
4@AllArgsConstructor
5@NoArgsConstructor
6public class Order {
7    @Id
8    @GeneratedValue(strategy = GenerationType.IDENTITY)
9    public Long orderId;
10
11    @ManyToOne
12    @JoinColumn(name="customer_id")
13    @JsonBackReference
14    private Customer customerId;
15
16    @CreationTimestamp
17    @Column(name="date_created", nullable = false, updatable = false)
18    private LocalDateTime dateCreated;
19
20    @ManyToOne
21    @JoinColumn(name="product_id")
22    private Product productId;
23}

In the Order class, there’s a column called customer_id that act as a foreign key referencing the Customer table. The@JoinColumn annotation is used to add this column to the Order table, withcustomer_id being the name of the column added. What SpringBoot does is that it will make use of the primary key of the referenced Entity(Customer) as the column that this foreign key will reference. On top of that, the@ManyToOne annotation highlights that many orders can belong to a single customer.

To avoid issues like infinite recursion when serializing, we use@JsonBackReference. Without it, serializing an order would include the associated customer, which would then reference its list of orders, creating a never-ending loop. @JsonManagedReference is the forward part of reference, the one that gets serialized normally.@JsonBackReference is the back part of reference; it’ll be omitted from serialization.

Explore the Code on GitHub

The complete source code for this article is available on GitHub. Feel free to clone or fork the repository and experiment with the code!

View on GitHub