Skip to content

Spring Data Jpa

in the application.properties file (which is discussed here) add the line server.port = 8081.

NOTE: If you do not have access to IntelliJ Ultimate, you can also run the application using Maven and the command line:mvn spring-boot: run.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Auto configuration

spring-boot-starter-data-jpa, like other starters, it simplifies the work of programmers by freeing them from having to configure certain things. This starter relieves us from the need to [configuration] (../jdbc_hibernate/hibernate/ basic_configuration.md) Hibernata and allows you to easily configure theDataSource (e.g. in the [application.properties] file (application_configuration.md # file- applicationproperties)).

Database types

Relational databases can be divided into two main groups, i.e. those that:

  • store data in memory (e.g. H2, HSQLDB, Apache Derby)
  • store data in files (MySQL, MariaDB, OracleDB, MSSQL)

spring-boot-starter-data-jpa will configureDataSource differently depending on what database drivers are on classpath:

  • if there is a in-memory base depending on the dependencies - connection to it will be configured automatically
  • if a database driver is found in dependencies, e.g. MySQL and H2 (in-memory), an attempt will be made to connect to the database "not in memory" (MySQL in this example)

Base H2

The H2 database is a very convenient database that is worth using during the software development process (eg in the [profile] (profile_i_scopy.md # profile) development). We can choose it during [bootstrapping] (spring_boot_basics.md#creating-projects) of the project. Default user is sa, password is empty, dialect used isdialect: org.hibernate.dialect.H2Dialect and driver is org.h2.Driver. By default, the database name is generated, and the address is listed in the logs during startup, e.g .:

2020-08-28 21:48:21.508  INFO 17728 --- [main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2'. Database available at 'jdbc:h2:mem:679bb4b4-7115-4e55-8eae-f423e8f97900'

The H2 database allows you to view its content using a browser. By default, however, this console is turned off. We can enable it and set the path to connect it with:

spring.h2.console.path=/h2
spring.h2.console.enabled=true

DataSource configuration

Automatically selected values representing [DataSource] (../ jdbc_hibernate/JDBC/connections.md) can be changed using the following [configuration] properties (application_configuration.md # applicationproperties-file):

spring.datasource.username
spring.datasource.url
spring.datasource.password
spring.datasource.driver-class-name

EntityManager

Thanks to the use of spring-boot-starter-data-jpa, we are able to inject the EntityManager object into any bean. It allows you to perform simple operations (CRUD) and query execution using the HQL language. Remember that the components representing the database layer should be better marked with the @Repository annotation instead of the@Component annotation. The following example shows its use:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "fruits")
public class Fruit {
  @Id
  @GeneratedValue
  private Long id;

  private String name;

  private Double weight;

  public Fruit(final String name, final Double weight) {
    this.name = name;
    this.weight = weight;
  }
}
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

@Repository
public class FruitRepository {

  private final EntityManager entityManager;

  public FruitRepository(final EntityManager entityManager) {
    this.entityManager = entityManager;
  }

  public Optional<Fruit> getById(final Long id) {
    return Optional.ofNullable(entityManager.find(Fruit.class, id));
  }

  public List<Fruit> findAll() {
    return entityManager.createQuery("SELECT f FROM fruits f", Fruit.class).getResultList();
  }

  public void delete(final Fruit fruit) {
    entityManager.remove(fruit);
  }

  public void deleteById(final Long id) {
    entityManager.createQuery("DELETE FROM fruits f WHERE f.id = :id")
        .setParameter("id", id)
        .executeUpdate();
  }

  public Fruit createFruit(final Fruit fruit) {
    entityManager.persist(fruit);
    return fruit;
  }
}
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DbInitializer implements CommandLineRunner {

  private final FruitRepository fruitRepository;

  public DbInitializer(final FruitRepository fruitRepository) {
    this.fruitRepository = fruitRepository;
  }

  @Override
  public void run(final String... args) throws Exception {
    final Fruit fruitA = new Fruit("Apple", 20.0);
    final Fruit fruitB = new Fruit("Banana", 18.0);
    fruitRepository.createFruit(fruitA); // (x)
    fruitRepository.createFruit(fruitB);
  }
}

The class DbInitializer, defined above, will throw an exception with the following when invoking the line of code marked with (x):

No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

This fact is due to the fact that nowhere in the above code have we defined the transaction.

@Transactional annotation

Manually creating and managing transactions can be cumbersome and the code can become unreadable. Spring, by using aspect programming (which is beyond the scope of this training), eliminates all of these problems. It offers an annotation @Transactional, which we can place over public methods or a class (which is equivalent to placing it on all public methods in the class). This annotation ensures that the code that uses it runs in the transaction. By default, if a method marked with this annotation is called and a transaction has already been opened, a new transaction will not be started.

NOTE: When importing the @Transactional annotation, we can choose between the one included in thejavax.transaction package or org.springframework.transaction.annotation. The framework will create a transaction if you choose any of them, however, the one in the org.springframework.transaction.annotation package offers more functionalities (which are beyond the scope of this training).

NOTE: The bug in the previous example can be corrected by adding an @Transactional annotation over the FruitRepository class.

JPA repositories

The database layer, ie the classes representing the repositories, usually have methods representing the base CRUD operations, the implementation of which most often looks identical. Therefore, the makers of the spring-boot-starter-data-jpa added functionality that allows you to implement repositories by defining an interface. Implementation of such interface will be generated, and access to it is provided by the proxy mechanism.

We have the option of extending one of the three interfaces:

  • CrudRepository
  • PagingAndSortingRepository
  • JpaRepository

The basic interface is CrudRepository which provides access to the methods:

  • save
  • saveAll
  • findById
  • existsById
  • findAll
  • findAllById
  • count
  • deleteById
  • deleteAll

It is worth paying attention to the save method, which is responsible for both creating and updating a record in the database. It therefore relieves us of the need to use the merge (update) andpersist (save) methods available on the EntityManager object.

The other two interfaces give access to additional methods, e.g .:

  • findAll(Sort)
  • findAll(Pageable
  • saveAndFlush
  • getOne
  • deleteInBatch
  • deleteAllInBatch

Extending one of the interfaces requires us to provide two generics:

  • entity identifier type
  • entity type

The example shows the use of such an interface:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "fruits")
public class Fruit {
  @Id
  @GeneratedValue
  private Long id;

  private String name;

  private Double weight;

  public Fruit(final String name, final Double weight) {
    this.name = name;
    this.weight = weight;
  }
}
import org.springframework.data.repository.CrudRepository;

public interface FruitRepository extends CrudRepository<Fruit, Long> {
}
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class DbInitializer implements CommandLineRunner {

  private final FruitRepository fruitRepository;

  public DbInitializer(final FruitRepository fruitRepository) {
    this.fruitRepository = fruitRepository;
  }

  @Override
  public void run(final String... args) throws Exception {
    Fruit apple = fruitRepository.save(new Fruit("apple", 23.1));
    Fruit banana = fruitRepository.save(new Fruit("banana", 20.1));

    fruitRepository.findAll().forEach(System.out::println); // Fruit(id=1, name=apple, weight=23.1) Fruit(id=2, name=banana, weight=20.1)
    System.out.println(fruitRepository.count()); // 2
    fruitRepository.deleteAll(List.of(apple, banana));
    System.out.println(fruitRepository.count()); // 0
  }
}

NOTE: JPA repositories do not need to be marked with any annotation. Their implementation will be in a context that we can inject via interface.

NOTES: Above the interfaces representing the repository we do not need to add annotation @Transactional.

Creating queries

JPA repositories have unique functionality. They allow you to create queries using the name of the method defined in the interface. Its implementation will be generated. When creating such inquiries, we must remember about certain rules:

  • while retrieving data, the method name must begin with the word find,read, query orget (find will be used in the examples). After that keyword, we insert the optional keyword followed by the word By
  • after the word By we give ( optional ) details of the query that can be identified with the part of SQL query, which follows the keyword WHERE.
    • these parts should point to field names of the entity whose value we are asking for (we start with a capital letter)
    • when we want to ask for the value of several fields, we combine the field names, just like in a regular SQL query, with the word
      • Or
      • And
    • depending on the value of how many fields we ask, the method should have an argument of the appropriate type for each of them (in order)

For example, for the Fruit entity from the previous example:

import org.springframework.data.repository.CrudRepository;

import java.util.Optional;

public interface FruitRepository extends CrudRepository<Fruit, Long> {
  Optional<Fruit> findByWeight(Double weight); // SELECT TOP 1 * FROM fruits WHERE weight = ?
  Optional<Fruit> findByName(String name); // SELECT TOP 1 * FROM fruits WHERE name = ?
  Optional<Fruit> findByWeightAndName(Double weight, String name); // SELECT TOP 1 * fruits WHERE weight = ? AND name = ?
  Optional<Fruit> findByNameOrWeight(String name, Double weight); // SELECT TOP 1 * fruits WHERE name = ? OR weight = ?
}

NOTE: If you define a method with an incorrect name, you will be informed about it when starting the application.

Return types

When creating queries using method names, we can insert several optional keywords between the word find andBy, which also have their SQL equivalents, e.g .:

  • Distinct
  • All
  • TopX, where X is a natural number

Depending on whether the query can return at most one record or many, we should select the returned data type appropriately. Some of the possibilities are:

  • if you want to download at most one object:

    • Optional
    • entity object (null in case no record found)
  • when downloading multiple records:

    • List<TypEncji>
    • Set<TypEncji>

For example:

import org.springframework.data.repository.CrudRepository;

import java.util.List;
import java.util.Optional;

public interface FruitRepository extends CrudRepository<Fruit, Long> {
  Optional<Fruit> findByWeight(Double weight);
  Fruit findByName(String name);
  List<Fruit> findAllDistinctByName(String name);
  List<Fruit> findTop3ByName(String name);
}

Advanced queries

In addition to the functionalities described above, JPA repositories also offer many keywords that can be used to filter the downloaded data even more effectively (or use other SQL keywords). Some of them are:

  • LessThan
  • GreaterThan
  • After
  • Before
  • StartingWith
  • EndingWith
  • Containing
  • In
  • NotIn
  • Like
  • NotLike
  • IgnoreCase
  • OrderBy

We place these words either after the field names or at the end of the query (such as OrderBy). The rules are very similar to those in SQL.

Below is an example of a repository that defines some more complex queries:

import org.springframework.data.repository.CrudRepository;

import java.util.List;
import java.util.Set;

public interface FruitRepository extends CrudRepository<Fruit, Long> {
  List<Fruit> findAllByNameStartingWithAndWeightBetweenOrderByNameDesc(String nameStartingWith, Double minWeight, Double maxWeight);
  List<Fruit> findAllByWeightLessThan(Double upperBound);
  List<Fruit> findFirstByNameContaining(String nameContaining);
  List<Fruit> findAllByNameIn(Set<String> names);
  List<Fruit> findAllByNameLike(String nameLike);
  List<Fruit> countDistinctByNameEndingWith(String nameEnding);
}

NOTE: IntelliJ Ultimate suggests syntax when creating queries inside JPA repository.

Custom Queries

Sometimes there is a need to write a query that is very long or that cannot be created with a method name and we are forced to use the syntax HQL. For this, we can define a method that has any name, but which is annotated with @Query. Inside the Query we put the body of the HQL query.

  • if the query has parameters, we put them as method arguments, and give the names inside the @Query annotation from the package org.springframework.data.jpa.repository
  • if the method modifies the database, the method is additionally marked with the annotation @Modifying
  • if we prefer to write the query using native SQL, we can set the value true in the fieldnativeQuery

The following example represents such a custom query:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface FruitRepository extends CrudRepository<Fruit, Long> {

  @Query(value = "SELECT f FROM fruits f WHERE f.name = :name", nativeQuery = false)
  List<Fruit> selectAllFruitsByProvidedName(@Param("name") String name);
}