You are on page 1of 47

Spring Data and Spring

Data Rest

1 / 47
Spring Data framework
Spring Data: https://spring.io/projects/spring-data
Spring Data’s mission is to provide a familiar and consistent,
spring-based programming model for data access while still
retaining the special traits of the underlying data store.
It makes it easy to use data access technologies, relational and
non-relational databases, map-reduce frameworks, and cloud-
based data services.

2 / 47
Features
Dynamic query derivation from repository method names
Support for transparent auditing (created, last changed)
Data Pagination

3 / 47
Most common Spring Data projects
Spring Data JPA - Spring Data repository support for JPA.
Spring Data MongoDB - Spring based, object-document support
and repositories for MongoDB.
Spring Data REST - Exports Spring Data repositories as
hypermedia-driven RESTful resources.
Spring Data Elasticsearch - Spring Data module for Elasticsearch.

4 / 47
Spring Data JPA

5 / 47
Features
Ready made CRUD provided functionality
Dynamic query derivation from repository method names
Manual queries @Query annotated queries
Support for transparent auditing (created, last changed)
Support for Querydsl predicates and thus type-safe JPA queries
Pagination support, dynamic query execution, ability to integrate
custom data access code

6 / 47
Development time

Start from eshop code of first lecture. Alternatively one may


wish to start from spring initilizr as an exercise.

At any case please add JPA , Postgresql , Web and


Lombok .

Next slides focus more at changes compared to the


application of the eshop application of the previous lecture.

The build.gradle ends up as follows:

7 / 47
plugins {
id 'org.springframework.boot' version '2.1.4.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'gr.rongasa'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starte
implementation 'org.springframework.boot:spring-boot-starte
implementation 'org.postgresql:postgresql'
implementation 'org.springframework.boot:spring-boot-starte
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-st
}

8 / 47
Application Configuration
(Postgresql Connection and JPA)
server:
port: 9090 # Set the server's port
servlet:
context-path: /jpa # Set context path of application serve
...

9 / 47
...
spring:
application:
name: e-shop # Set the application name.
data:
jpa:
repositories:
enabled: true
datasource: # Setup datasource
hikari:
connection-timeout: 20000
maximum-pool-size: 5
url: jdbc:postgresql://localhost:5432/eshop
username: eshop
password: eshop@@@
jpa: #JPA properties
hibernate:
ddl-auto: update
show-sql: true
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQL95Dialec
open-in-view: false # Significant attribute. Dont open tr
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true

10 / 47
The Domain object
package gr.rongasa.eshop.domain;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;

@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class Inventory {
@Id
@GeneratedValue
private Long id;
private String name;
private Long amount;
private String description;
private String type;
private BigDecimal cost;
} 11 / 47
Repository
import gr.rongasa.eshop.domain.Inventory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface InventoryRepository extends JpaRepository<Inve


Page<Inventory> findAllByType(Pageable pageable, String typ
Page<Inventory> findAllByName(Pageable pageable, String typ
}

12 / 47
Database Configuration
package gr.rongasa.eshop.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpa

@Configuration
@EnableJpaRepositories(basePackages = {"gr.rongasa.eshop.reposi
@Slf4j
public class DatabaseConfiguration {

@Bean
public CommandLineRunner stupidBean(){
return args -> log.info("JPA java code started");
}
}

13 / 47
Result
The conversion from Spring Data elasticSearch to spring data JPA
is done. This is what is meant with familiar and consistent model
for data layer access. Consider how few changes were made to
move from two completely different database technologies.
We could allow the concurrent existence of elasticSearch and JPA
connection
We changed domain id from String to Long. Elastic search
performs better/easier with String id while
Be careful with the @Id and from which path this is imported from.
javax.persistence.Id; is for JPA and
org.springframework.data.annotation.Id; is for non
relational databases.

14 / 47
Means of Database Queries
The repository proxy has two ways to derive a store-specific query
from the method name:

By deriving the query from the method name directly.


By using a manually defined query.

15 / 47
Query from method name
findFirst/ findTop
findAllBy or findBy
distinct
existsBy
delete
count
orderBy
asc, desc

e.g.

Page<Inventory> findAllByType(Pageable pageable, String type);


Page<Inventory> findAllByName(Pageable pageable, String type);
void deleteByType(String type);
boolean existsByType(String type);
long countByType(String type);

16 / 47
Supported method Names
keywords
Keyword Keyword Keyword Keyword

And LessThan After Like


Or LessThanEqual Before NotLike
Is, Equals GreaterThan IsNull StartingWith
Between GreaterThanEqual IsNotNull,NotNull EndingWith
Containing OrderBy Not In
NotIn True False IgnoreCase

Page<Inventory> findByTypeAndAmountGreaterThanOrderByCost(P
Optional<Inventory> findFirstByNameAndTypeOrderByCost(Strin
Page<Inventory> findDistinctTop2ByAmountLessThanOrderByAmou
Page<Inventory> findDistinctTop2ByAmountLessThanOrderByAmou
Page<Inventory> findByNameContaining(Pageable pageable, Str
Page<Inventory> findByNameEndingWith(Pageable pageable, Str17 / 47
Query
JPQL or native queries are supported in Spring data repositories

e.g.

@Query("select i from Inventory i where i.description=?1")


Inventory getInventory(String details);

@Query(value = "select * from Inventory where i.description=?1"


Inventory getInventoryByNativeQuery(String details);

@Query("select i from Inventory i where i.description=descr")


Inventory getInventoryByNamedParam(@Param("descr") String descr

18 / 47
Query
Propose to avoid Queries and even more native queries. However,
although spring data provides practically almost everything,
sometimes JPA query may be the only way.

Hard to move from one database engine to another


Less readable
More difficult to detect issues before running the application

One reason that query may be needed is when we wish to query


something like the following (entity.x=:a OR entity.x=:b)
AND (entity.y=:c OR entity.y=:d)

19 / 47
Entity Graph
*ToMany relationships are lazy fetch. This means you cannot get
i.e. the members without a transaction
Repository methods open a transaction inside method call and
close it after method exit.
EntityGraph is a performant way of eagerly fetching inner entities
after repository method returns

@Entity
public class GroupInfo {

// default fetch mode is lazy.


@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();

}

@Repository
public interface GroupRepository extends CrudRepository<GroupIn

@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
} 20 / 47
Named Entity Graph
Instead of constructing and using EntityGraph inside repository one
create named entity graphs and then use these inside the repository

@Entity
@NamedEntityGraph(
name = "members-graph",
attributeNodes = {
@NamedAttributeNode("members")
}
)
public class GroupInfo {
// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();

}

@Repository
public interface GroupRepository extends CrudRepository<GroupIn
@EntityGraph(value = "members-graph")
GroupInfo getByGroupName(String name);
}
21 / 47
Common Mistakes/Improvements
JPA prerequisite information

@OneToOne : Eager fetch


@OneToMany : Lazy fetch
Typically relationships are bidirectional (when possibly make them
unidirectional).

Attention:

Bidirectional relationships during update and submit and first level


cache. One needs to update both directions to avoid problems.
Take special care with equals, hashCode and toString overrides at
entity level when having relationships (especially now with
lombok)
Danger of stack overflow
Danger of performance issues simply when adding just
logging
private methods annotated with transaction will not cope into
opening a transaction.
22 / 47
Common Mistakes/Improvements
Use transactions at Service level at most Avoid opening
transaction at controller.
Mapping of database entities into data transfer objects is a good
way of controlling transactions
Only get from entity what is needed
Set into entity before saving into database bidirectional
relationships
Usage of EntityGraph a good way of keeping transaction as small
as possible
When using relational database utilize database versioning and
migration tools like Liquidbase or Flyway
Don't skip this step as migrating from one version to another
will be problematic without these tools and also it is harder to
introduce these tools at a latter stage.

23 / 47
Spring Data Rest

24 / 47
Spring Data Rest is an 'extension' of Spring Data that exposes over
REST the spring data methods that exist in spring data repositories.

Impressive at first look and useful for rapid


development results.
Use it only for testing, development prototype or only
when product is really simple
If used in bigger products you bind domain model
with rest consumer. Architecturally wise this is huge
mistake that sooner or latter it will add huge
complexity.
Spring data rest is the best demonstration of
HATEOAS.

25 / 47
Spring Data Rest
Add in gradle the following dependency

implementation 'org.springframework.boot:spring-boot-starter-da

Add in all custom repository methods that you wish to expose over
rest and have method parameters the @Param annotation.

public interface GroupInfoRepository extends JpaRepository<Grou


@EntityGraph(value = "members")
Page<GroupInfo> findAllByMembersName(@Param("name") String
}

Add the spring data rest path in application.yml

data:
rest:
base-path: api

26 / 47
Spring Data Rest - HATEOAS
curl -X GET http://localhost:9090/jpa/api

Notice that spring data repositories are exposed over rest.

{
"_links": {
"groupInfoes": {
"href": "http://localhost:9090/jpa/api/groupInfoes{
"templated": true
},
"groupMembers": {
"href": "http://localhost:9090/jpa/api/groupMembers
"templated": true
},
"inventories": {
"href": "http://localhost:9090/jpa/api/inventories{
"templated": true
},
"profile": {
"href": "http://localhost:9090/jpa/api/profile"
}
}
} 27 / 47
HATEOAS/HAL

Spring data REST exposes all entities using


HATEOAS.
HATEOAS is a REST API architecture concept which
defines that REST resource models include
hypermedia links in such a way that clients can
navigate to the complete Rest Interface.
Clients may start from root context path and by
following hateoas link names expose the complete API.

28 / 47
Spring Data REST
"inventories": {
"href": "http://localhost:9090/jpa/api/inventories{?pag
"templated": true
},

tempalated means that page, size, sort are part of query template.

i.e. http://localhost:9090/jpa/api/inventories?
page=1&size=10&sort=id,asc

29 / 47
{
"_embedded": { "inventories": [] },
"_links": {
"first": {
"href": "http://localhost:9090/jpa/api/inventories?
},
"prev": {
"href": "http://localhost:9090/jpa/api/inventories?
},
"self": {
"href": "http://localhost:9090/jpa/api/inventories"
},
"last": {
"href": "http://localhost:9090/jpa/api/inventories?
},
"profile": {
"href": "http://localhost:9090/jpa/api/profile/inve
},
"search": {
"href": "http://localhost:9090/jpa/api/inventories/
}
},
"page": {
"size": 10,
"totalElements": 0,
"totalPages": 0,
"number": 1
}
} 30 / 47
Create new object
curl -X POST http://localhost:9090/jpa/api/inventories -H 'Cont

-d '{
"name": "book",
"amount": 10,
"description": "custom rwritten book",
"type": "book",
"cost": 10.45
}'

31 / 47
Result

{
"name": "book",
"amount": 10,
"description": "custom rwritten book",
"type": "book",
"cost": 10.45,
"_links": {
"self": {
"href": "http://localhost:9090/jpa/api/inventories/
},
"inventory": {
"href": "http://localhost:9090/jpa/api/inventories/
}
}
}

32 / 47
Update new Object
curl -X PUT \
http://localhost:9090/jpa/api/inventories/3 \
-H 'Content-Type: application/json' \
-d '{
"name": "book",
"amount": 10,
"description": "custom rwritten book",
"type": "book",
"cost": 10.45
}'

33 / 47
{
"name": "book",
"amount": 10,
"description": "custom rwritten book",
"type": "book",
"cost": 10.45,
"_links": {
"self": {
"href": "http://localhost:9090/jpa/api/inventories/
},
"inventory": {
"href": "http://localhost:9090/jpa/api/inventories/
}
}
}

34 / 47
Search Hypermedia Link
Ensure you have added in spring data custom repository method
@Param annotation at method parameters
After creating an Inventory object locate hypermedia link named
search and make a GET request. i.e.
http://localhost:9090/jpa/api/inventories/search

35 / 47
All custom methods created are exposed and can be used

{
"_links": {
"updateMediaType": {
"href": "http://localhost:9090/jpa/api/inventories/
"templated": true
},
"findByAmountIsBetween": {
"href": "http://localhost:9090/jpa/api/inventories/
"templated": true
},
"findByTypeAndAmountGreaterThanOrderByCost": {
"href": "http://localhost:9090/jpa/api/inventories/
"templated": true
},
...
"self": {
"href": "http://localhost:9090/jpa/api/inventories/
}
}
}

36 / 47
Spring Data REST Tips
And Tricks

37 / 47
You can annotate as @RestResource(exported = false) a
Repository (method or class) to define it as not exposed over web
You can configure Repositoriies (i.e. expose id) by using
RepositoryRestConfigurer

@Configuration
public class RestConfiguration {

@Bean
public RepositoryRestConfigurer repositoryRestConfigurer()
return new RepositoryRestConfigurer() {
@Override
public void configureRepositoryRestConfiguration(Re
config.exposeIdsFor(Inventory.class);
}
};
}
}

38 / 47
You can enable HAL Browser to assist with spring data rest
experience:

implementation 'org.springframework.data:spring-data-rest-hal-b

Now open the Spring Data Rest base url via browser and
check the result.

A UI regarding exposed rest methods is provided.

39 / 47
Spring Data Tips And
Tricks

40 / 47
You can load initial data via CommandLineRunner

@Component
@RequiredArgsConstructor
public class DatabaseDataInitialization implements CommandLineR
private final InventoryRepository inventoryRepository;

@Override
public void run(String... args) {
if (inventoryRepository.countByType("book")==0){
inventoryRepository.saveAll(Stream.of("Spring Boot"
.collect(Collectors.toList()));
}
}
}

41 / 47
Spring Data MongoDB

42 / 47
Lets switch this project now to NoSQL/MongoDB.
Then we have seen the same project in ElasticSearch (1st
lecture), in JPA (Postgresql) and MongoDB.

43 / 47
Prepare Dependencies
In gradle remove 'org.springframework.boot:spring-boot-starter-
data-jpa' and 'org.postgresql:postgresql' from dependencies
In gradle add 'org.springframework.boot:spring-boot-starter-data-
mongodb' as dependency

Prepare Domain model


In Domain objects replace @Entity, @Table with @Document
In Domain objects remove @GeneratedValue and
@ManyToMany, @OneToOne
In Domain objects replace id of type Long with String, BigInteger,
or ObjectID
Remove from Domain objects and repositories anything that has
to do with EntityGraph (@EntityGraph and @NamedEntityGraph)
Ensure @Id is from import org.springframework.data.annotation.Id

44 / 47
Prepare Repositories
In repositories replace JpaRepository interface with
MongoRepository
In repositories remove Modifying queries
In Repositories native queries are not considered (all queries are
native)
In Repositories modify jpa queries to match mongodb query
language
In database configuration replace EnableJpaRepositories with
EnableMongoRepositories

45 / 47
Exercise 1
Purpose: Gain experience with Spring Data and Spring Data Rest.

46 / 47
Description
You need to extend the simple Library Management System (exercise
of first lecture) and add the same information added into elasticsearch
into postgresql database. This means that it has been decided to
support two databases in parallel.

Postgresql will not be exposed over web using Spring Data Rest.
Elasticsearch database will be exposed over REST.
One extra method supported over web will be the post method
''/synch' which will synchronize all data from Postgresql to elastic
search.
From elasticSearch repositories it has been decided to expose
over rest all the get methods but hide/disable the database
modifying methods and allow modification only via the existing
controller.
It is decided to use flyway as a migration tool for JPA database.

47 / 47

You might also like