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