Spring Data JPA / Design Entities

In this post, we will go into how to design our entities for a basic project for the project we created here using Spring Data JPA. To be more precise I designed a small scenario. We will develop a basic application which stores flights and passengers. You can find class and ER diagrams in following figures.

Here we have 3 basic tables:

  1. Person table which will store our passenger names
  2. Flight table which will store our flights
  3. City table which will store our airports or ports

To make application more understandable, I will follow fail/solve/continue approach (not sure if such an approach exists). First I will write the most basic code, then run it and let it fail, then solve the problem and run it again till reach the desired design.

Writing Entity Classes

First we need to write our entity classes. If you are not familiar with entity class, we can say that entity class represents a table in db. Spring Data JPA uses hibernate library as underlying JPA provider. I will give information time to time during article.

You can find our classes in following code samples

package com.enginaar.spring.data.domain;

import javax.persistence.Entity;
import lombok.Data;

@Entity
@Data
public class Person {
    
	private long id;
	private String name;
	private String lastName;

}
package com.enginaar.spring.data.domain;

import java.util.Date;
import javax.persistence.Entity;
import lombok.Data;

@Data
@Entity
public class Flight {
    
    private long id;
    private long person_id;
    private long origin;
    private long destination;
    private Date date;
    
}
package com.enginaar.spring.data.domain;

import javax.persistence.Entity;
import lombok.Data;

@Entity
@Data
public class City {
    
    private long id;
    private String name;

}   

Now we have three class files which represent three tables in database. Hibernate uses code-first-approach which means we are coding our application and hibernate takes care of database part. It creates tables, foreign keys, indexes etc.

Please keep in mind that we do not write any setter/getter code for class fields. Lombok project settles that part on our behalf.

If we would like to go for run the application as is now, we will see a problem.

Caused by: org.hibernate.AnnotationException: No identifier specified for entity: com.enginaar.spring.data.domain.City

Our problem here is that JPA (Spring Data JPA) concept needs an ID field per entity class and we do not have an ID (primary key) fileld for any entity. We need to specify ID fields using @ID annotation.

@ID annotation is a field based annotation which defines a property to a field. As best practice, an ID field in database must have a unique vvalue and generally it is a generated value. For this scenario, hibernate provides another annotation which is @GeneratedValue with option of how it is being generated. If the database supports identity field which is autoincrement, you can use @GeneratedValue(strategy = GenerationType.IDENTITY). I added this annotation to id field. You can see new version in following code snippets

package com.enginaar.spring.data.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.Data;

@Entity
@Data
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    private String lastName;
    
}
package com.enginaar.spring.data.domain;

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;

/**
 *
 * @author kenanerarslan
 */
@Data
@Entity
public class Flight {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private long person_id;
    private long origin_id;
    private long destination_id;
    private Date date;

}
package com.enginaar.spring.data.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;

/**
 *
 * @author kenanerarslan
 */
@Entity
@Data
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
}

Now we are ready to run the application. Let’s run it.

I run it checked the logs and see no clue what’s happening. I decided to add some extra fields to my application.properties file to have better visibility.

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create

When I start application again, I saw the following logs

SQL query logs
SQL query logs

“drop table…
create table….” sql queries show us that we have successfully created our tables.

Let’s see what has been created, I am using dbeaver, you can use any database client application as your wish.

The first ER diagram on DB
The first ER diagram on DB

As you can see we created the tables in the database. We have one missing thing, relations. When we take a look our ER diagram at the beginning of this page, we can see that we designed our database tables related to each other as well as entity classes. I continued without relation just to show you what happens when we do not add our relations to our entity classes.

If you would like to continue with this structure, yes you can but not recommended, you are free. Just play around.

We continue to create our relations. When we take a look our class diagram, we see each person has 0 or more flights. Lets code this part first.

 package com.enginaar.spring.data.domain;

import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import lombok.Data;

@Entity
@Data
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    private String lastName;
    
    @OneToMany(mappedBy = "person")
    private Collection<Flight> flights;
    
}

In person class we define a new field named flights annotated by @OneToMany. This annotation says that a person can have multiple flights. The flights to be mapped by person.

package com.enginaar.spring.data.domain;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import lombok.Data;

/**
 *
 * @author kenanerarslan
 */
@Data
@Entity
public class Flight {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    
    @ManyToOne
    @JoinColumn(name = "person_id")
    private Person person;
    
    @ManyToOne
    @JoinColumn(name = "origin")
    private City origin;
    
    @ManyToOne
    @JoinColumn(name = "destination")
    private City destination;
    
    private Date date;

}
  • For person, we say that a person can have multiple flights and from flight side a flight must have one person.
  • For origin and destination, we say that a flight must have one origin and one destination. I did not map the city and flights from city side because city table will have foreign key for both origin and destination

When we run application one more time we see following logs that show us our foreign keys are also in place.

Hibernate: create table city (id bigint not null auto_increment, name varchar(255), primary key (id)) engine=MyISAM<br>Hibernate: create table flight (id bigint not null auto_increment, date datetime, destination bigint, origin bigint, person_id bigint, primary key (id)) engine=MyISAM<br>Hibernate: create table person (id bigint not null auto_increment, last_name varchar(255), name varchar(255), primary key (id)) engine=MyISAM<br>Hibernate: alter table flight add constraint FK718pf6d3u5y2vqcddpxqvo5j foreign key (destination) references city (id)<br>Hibernate: alter table flight add constraint FKlq48dussfltlg8du9dnsxv518 foreign key (origin) references city (id)<br>Hibernate: alter table flight add constraint FK9okb94gffmibej4rxu7auvab4 foreign key (person_id) references person (id)