How to get streaming results with Spring JPA, Java 8 Stream and PostgreSQL | Spring Boot

In tradition approach, implementing Data Access Layer makes lots of boilerplate code. Spring Data provides us Spring JPA which supports the ways to write interface for repositories and custom finder methods. The implementation will be done automatically by Spring Framework.

The tutorial shows you how to make streaming results with Spring JPA, Java 8 Stream and PostgreSQL using Spring Boot.

Related articles:
Java 8 Streams
How to use Spring JPA MySQL | Spring Boot
How to use Spring JPA with PostgreSQL | Spring Boot
@DataJPATest with Spring Boot

I. Technology

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– Spring Boot: 1.5.1.RELEASE

II. Overview

1. Project Structure


– Class Customer corresponds to entity and table customer, it should be implemented Serializable.
CustomerRepository is an interface extends CrudRepository, will be autowired in WebController for implementing repository methods and custom finder methods.
WebController is a REST Controller which has request mapping methods for RESTful requests such as: save, findall, findalllastname, findbylastname.
– Configuration for Spring Datasource and Spring JPA properties in application.properties
Dependencies for Spring Boot and PostgreSQL in pom.xml

2. Step to do

– Create Spring Boot project & add Dependencies
– Configure Spring JPA
– Create DataModel Class
– Create Spring JPA Repository Interface
– Create Web Controller
– Create PostGreSQL table
– Run Spring Boot Application & Enjoy Result

III. Practice

1. Create Spring Boot project & add Dependencies

Open Spring Tool Suite, on Menu, choose File -> New -> Spring Starter Project, then fill each fields.
Click Next, in SQL: choose JPA and PostgreSQL, in Web: choose Web.
springjpa-postgresql-configdependencystarter
Click Finish, then our project will be created successfully.

Open pom.xml and check Dependencies:

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

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

These dependencies were auto-generated by the configuration we have done before.

2. Configure Spring JPA

Open application.properties


spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123
spring.jpa.generate-ddl=true

3. Create DataModel Class

Under package model, create class Customer.

Content of Customer.java:


package com.javasampleapproach.jpastream.model;

import java.io.Serializable;

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

@Entity
@Table(name = "customer")
public class Customer implements Serializable {

	private static final long serialVersionUID = -3009157732242241606L;
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;

	@Column(name = "firstname")
	private String firstName;

	@Column(name = "lastname")
	private String lastName;

	protected Customer() {
	}

	public Customer(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
	}
}

Annotation @Entity indicates that Customer is an Entity and @Table specifies the primary table (name customer) for the annotated @Entity.

@ID specifies the primary key and @GeneratedValue indicates generation strategy for value of primary key.

@Column: mapped column (in the table) for persistent fields (in Java class).

We have 2 constructor methods:
protected constructor will be used by Spring JPA.
public constructor is for creating instances.

4. Create Spring JPA Repository Interface

This interface helps us do all CRUD functions for class Customer.


package com.javasampleapproach.jpastream.repo;

import java.util.stream.Stream;

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

import com.javasampleapproach.jpastream.model.Customer;

public interface CustomerRepository extends CrudRepository {

	@Query("select c from Customer c")
	Stream findAllCustomers();
	
	@Query("select c.lastName from Customer c")
	Stream findAllandShowLastName();

	@Query("select c from Customer c where c.lastName = ?1")
	Stream findByLastName(String lastName);
}

5. Create Web Controller

The controller receives requests from client, using repository to update/get data and return results.

Content of WebController.java


package com.javasampleapproach.jpastream.controller;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.javasampleapproach.jpastream.model.Customer;
import com.javasampleapproach.jpastream.repo.CustomerRepository;

@RestController
public class WebController {

	@Autowired
	CustomerRepository repository;

	@RequestMapping("/save")
	public String process() {
		repository.save(new Customer("Jack", "Smith"));
		repository.save(new Customer("Adam", "Johnson"));
		repository.save(new Customer("Kim", "Smith"));
		repository.save(new Customer("David", "Williams"));
		repository.save(new Customer("Peter", "Davis"));
		return "Done";
	}

	@Transactional(readOnly = true)
	@RequestMapping("/findall")
	public String findAllByStream() {
		List mapstream = Collections.emptyList();

		try (Stream stream = repository.findAllCustomers()) {
			mapstream = stream.map(customer -> customer.toString()).collect(Collectors.toList());
		}

		return mapstream.toString();
	}
	
	@Transactional(readOnly = true)
	@RequestMapping("/findalllastname")
	public String findAllLastNameByStream() {
		List mapstream = Collections.emptyList();;

		try (Stream stream = repository.findAllandShowLastName()) {
			mapstream = stream.collect(Collectors.toList());
		}

		return mapstream.toString();
	}

	@Transactional(readOnly = true)
	@RequestMapping("/findbylastname")
	public String fetchDataByLastNameWithStream(@RequestParam("lastname") String lastName) {
		List mapstream = Collections.emptyList();

		try (Stream stream = repository.findByLastName(lastName)) {
			mapstream = stream.map(customer -> customer.toString()).collect(Collectors.toList());
		}

		return mapstream.toString();
	}

}

For the methods which are annotated by @RequestMapping, we have used some methods of autowired repository which are implemented interface CrudRepository:


Stream findAllCustomers();
Stream findAllandShowLastName();
Stream findByLastName(String lastName);

*Notes:
– For Spring Boot 1.5, we must surround high-level methods (which call repository method) by a (read-only) @Transactional annotation to keep the connection open so that the Stream can actually be consumed.
– For Spring Boot 1.4, we don’t need to do that.

6. Create PostGreSQL table

Open pdAdmin III, use SQL Editor and make a query to create customer table:


CREATE TABLE customer(
    id BIGINT PRIMARY KEY NOT NULL,
    firstname VARCHAR(100),
    lastname VARCHAR(100)
);

7. Run Spring Boot Application & Enjoy Result

– Config maven build:
clean install
– Run project with mode Spring Boot App
– Check results:

Request 1
http://localhost:8080/save
The browser returns Done and if checking database testdb with table customer, we can see some data rows has been added:

Request 2
http://localhost:8080/findall


[Customer[id=1, firstName='Jack', lastName='Smith'], Customer[id=2, firstName='Adam', lastName='Johnson'], Customer[id=3, firstName='Kim', lastName='Smith'], Customer[id=4, firstName='David', lastName='Williams'], Customer[id=5, firstName='Peter', lastName='Davis']]

Request 3
http://localhost:8080/findalllastname


[Smith, Johnson, Smith, Williams, Davis]

Request 4
http://localhost:8080/findbylastname?lastname=Smith

[Customer[id=1, firstName='Jack', lastName='Smith'], Customer[id=3, firstName='Kim', lastName='Smith']]

IV. Source Code

SpringJPAStream



By grokonez | February 8, 2017.

Last updated on April 22, 2021.



Related Posts


6 thoughts on “How to get streaming results with Spring JPA, Java 8 Stream and PostgreSQL | Spring Boot”

  1. Hi,
    I was wondering how to use the stream to deal with big file upload and download in a full restful service.
    I am crunching some code but it does not work with stream. It would be nice to create an asynchronous bridge between the controller that upload and the service that is going to store to the db the bytes.
    I would like to get the same thing when we download. I would not like to not use hibernate or openSessionInView…
    I will post some code later on..

    1. Hi Slava,

      When you return a List, it may throw an OutOfMemoryException and slow performance issue when dealing with large data processing.
      SpringJPA provides a new feature to select data by using Stream of Java8.

      Regards,
      JSA

      1. This doesn’t solve the problem of OutOfMemoryException, it just delays it. You’re placing the results in a Collection and not “streaming” the results to the browser — the results are still being built up in memory before handing off to the client.

Got Something To Say:

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

*