Spring Data JPA / Repositories / CrudRepository

We have created our Spring Data project, developed entities, now we need to find out how to connect to database from our code and do basic CRUD operations using Spring Data JPA Repositories. For the one who are not familiar CRUD word, it is acronym of Create/Read/Update/Delete. To develop our application in a structured way we are going to use n-tier application approach. First lets remember n-tier architecture.

N-Tier Architecture

n-tier architecture diagram
N-Tier Architecture

In previous diagram, we see an application structure. Let’s go over one by one

  • Presentation Layer: Application’s user interaction part. It takes input from user pass it to our backend application using different techniques like rest, soap, RPC etc.
    • Web applications use web pages (html, js etc.)
    • For Desktop applications, it can be a java, .net or C++ application
    • Mobile applications can be native android, ios app or hybrid app
  • Business Layer: Application’s logic part. For example; if you are developing a flight ticket application, you need to do some operations on availability of your flights, bank accounts, airports etc. To do this operations you need to interact lots of services and may need to interact with your databases more than once.
  • Data Access Layer: Application’s database touching part. To persist your data, to query records, or to update them, you need to do interact your database each time. Here our database connecting codes will be located.

IMPORTANT NOTE: In computer programming, everything is about harmony and order. That means you can develop application without n-tier architecture or abstractions. You can do everything in a single file but don’t. Spring is here to help you for developing your application more structured and clean way, just use it.

Developing Data Access Layer/Repositories

Using Spring Data JPA library, it is so easy to develop a data access layer. Sometimes I think it became easier than it needs to be.

Our entity classes are part of data access layer and the other part is our repository classes which connect to database and do the operations on our behalf. The difference between repository and entity classes is that entity does not have any method to implement whereas repository provides methods to handle how to store/read our data.

In the earlier version of Spring, we needed to write down more code to handle CRUD operations, but as of today, we are only developing required interfaces and let spring implement them using some structured rules. First let’s remember our class diagram

er/class diagram

We have three classes/tables, so we need to develop three database access. I will call database access part as repository from now on.

First our package will be com.enginaar.spring.data.repository and classes are PersonRepository, FlightRepository, CityRepository. Spring provides an interface for us to extend our repository classes so that it can help us implement our interfaces. That is CrudRepository which handles;

  • Saves the given entity.
  • Fetches the entity identified by the given id.
  • List all entities.
  • Gets the number of entities.
  • Deletes the given entity.
  • Indicates whether an entity with the given id exists.

Here we have our interfaces,

// CityRepository.java
package com.enginaar.spring.data.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.enginaar.spring.data.domain.City;

@Repository
public interface CityRepository extends CrudRepository<City, Long>{}
// FlightRepository.java
package com.enginaar.spring.data.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.enginaar.spring.data.domain.Flight;

@Repository
public interface FlightRepository extends CrudRepository<Flight, Long>{}
// PersonRepository.java
package com.enginaar.spring.data.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.enginaar.spring.data.domain.Person;

@Repository
public interface PersonRepository extends CrudRepository<Person, Long> {}

Well! We have our repository interfaces now. I have especially 3 different point to emphesize.

  1. CrudRepository interface has basic CRUD operations and we extend our interface from it.
  2. @Repository annotation tells Spring this is an interface you need to manage it.
  3. The most important one!! We will not write any implementation for these interfaces. For the first time I wrote this interfaces I have tried to understand what’s going on in the back. Because I have an interface without its implementation.

Spring generates required implementation code for these interfaces when our application is up.

Now we have an application which has database connection, classes that implements CRUD operations. To see what we have complete I created a listener class which will be triggered when application starts.

// ApplicationListener.java
package com.enginaar.spring.data.listener;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import com.enginaar.spring.data.domain.City;
import com.enginaar.spring.data.domain.Flight;
import com.enginaar.spring.data.domain.Person;
import com.enginaar.spring.data.repository.CityRepository;
import com.enginaar.spring.data.repository.FlightRepository;
import com.enginaar.spring.data.repository.PersonRepository;

@Component
public class ApplicationListener {

	@Autowired
	private PersonRepository persons;
	@Autowired
	private CityRepository cities;
	@Autowired
	private FlightRepository flights;
	
	@EventListener(ApplicationReadyEvent.class)
	public void doSomethingAfterStartup() {
		Person p = new Person();
		p.setName("Kenan");
		p.setLastName("Erarslan");
		
		persons.save(p);
		
		City a = new City();
		a.setName("Ankara");
		City i = new City();
		i.setName("İstanbul");
		cities.save(a);
		cities.save(i);
		
		Flight f = new Flight();
		f.setDate(new Date());
		f.setPerson(p);
		f.setOrigin(a);
		f.setDestination(i);
		
		flights.save(f);
	}

}

Here I created a person named Kenan, two cities named Ankara, Istanbul and a Flight for Kenan from Ankara to Istanbul. Let’s see what is going on when application starts.

application log
Application log
  • drop tables, if tables exists drop them
  • create tables, creates our tables based on entity classes
  • alter tables, adds constraints (foreign keys between tables)
  • insert intos, inserts our records into database which is coded in listener class.
person table result
Saved user
city table result
Saved cities
flight table result
Saved flight

As we can see

  • Kenan’s id is 1 in Person table
  • Ankara’s id is 1 in City table
  • İstanbul’s id is 2 in City table
  • A flight on 2020-05-25 from Ankara (1) to İstanbul (2) for Kenan (1) is added to Flight table.