Canonical _links with Spring HATEOAS - spring

We're building a RESTful web service similiar to the spring.io guide "Accessing JPA Data with REST". To reproduce the sample outputs below, it suffices to add a ManyToOne-Relation to Person as follows:
// ...
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#ManyToOne
private Person father;
// getters and setters
}
A GET request after adding some sample data yields:
{
"firstName" : "Paul",
"lastName" : "Mustermann",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
},
"father" : {
"href" : "http://localhost:8080/people/1/father"
}
}
}
But, given Paul's father is stored with ID 2, our desired result would be the canonical url for its relation:
// ...
"father" : {
"href" : "http://localhost:8080/people/2"
}
// ...
This of course will cause problems if father is null on some persons (OK, this does not make much sense here... ;)), but in this case we would like to not render the link in JSON at all.
We already tried to implement a ResourceProcessor to achieve this, but it seems that by the time the processor is called the links are not populated yet. We managed to add additional links pointing to the desired canonical url, but failed to modify the somehow later added links.
Question: Is there a generic approach to customize the link generation for all resources?
To clarify our need for canonical URLs: We use the SproutCore Javascript framework to access the RESTful web service. It uses an "ORM-like" abstraction of data sources for which we've implemented a generic handler of the JSON output Spring produces. When querying for all persons, we would need to send n*(1+q) requests (instead of just n) for n persons with q relations to other persons to sync them to the client side data source. This is because the default "non-canonical" link contains absolutely no information about a father being set or the father's id. It just seems that this causes a huge amount of unnecessary requests which could be easily avoided if the initial response would contain a little more information in the first place.
Another solution would be to add the father's id to the relation, e.g.:
"father" : {
"href" : "http://localhost:8080/people/1/father",
"id" : 2
}

There is a discussion somewhere Spring Data Rest team explained why properties are rendered as links that way. Having said you could still achieve what you like by suppressing link generated by SDR and implementing ResourceProcessor. So your Person class would look like below. Note the annotation #RestResource(exported = false) to suppress link
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#ManyToOne
#RestResource(exported = false)
private Person father;
// getters and setters
}
Your ResourceProcessor class would look like
public class EmployeeResourceProcessor implements
ResourceProcessor<Resource<Person>> {
#Autowired
private EntityLinks entityLinks;
#Override
public Resource<Person> process(Resource<Person> resource) {
Person person = resource.getContent();
if (person.getFather() != null) {
resource.add(entityLinks.linkForSingleResour(Person.class, person.getFather().getId())
.withRel("father"));
}
return resource;
}
}
The above solution works only if father value is eagerly fetched along with Person. Otherwise you need to have property fatherId and use it instead of father property. Don't forget to use Jackson #ignore... to hide fatherId in response JSON.
Note: I haven't tested this myself but guessing it would work

Since I had the same problem, I created a Jira issue at spring-data-rest:
https://jira.spring.io/browse/DATAREST-682
If enough people vote for it, perhaps we can convince some of the developers to implement it :-).

It's weird that you're pushing to display the canonical link. Once that resource at /father is retrieved the self link should be canonical...but there's really not a good reason to force the father relationship to be canonical...maybe some cacheing scheme?
To your specific question...you're relying on auto-generated controllers so you've given up the right to make decisions about a lot of your links. If you were to have your own PersonController than you would be more in charge of the link structure. If you created your own controller you could then use EntityLinks https://github.com/spring-projects/spring-hateoas#entitylinks with the Father's ID..IE
#Controller
#ExposesResourceFor(Person.class)
#RequestMapping("/people")
class PersonController {
#RequestMapping
ResponseEntity people(…) { … }
#RequestMapping("/{id}")
ResponseEntity person(#PathVariable("id") … ) {
PersonResource p = ....
if(p.father){
p.addLink(entityLinks.linkToSingleResource(Person.class, orderId).withRel("father");
}
}
}
But that seems like a lot of work to just change the URL

Related

Spring boot REST API best way to choose in client side which field to load

Hi I have implemented a mock solution to my problem and I'm pretty sure something better already exist.
Here's that I want to achieve :
I have created a point to load categories with or without subCategories
/api/categories/1?fields=subCategories
returns
{
"id":"1",
"name":"test",
"subCategories":[{
"id":"1",
"name":"test123"
}]
}
/api/categories/1
returns
{
"id":"1",
"name":"test"
}
My entities
#Entity
class Category{
#Id
private String id;
private String name;
private Set<SubCategory> subCategories;
}
#Entity
class SubCategory{
#Id
private String id;
private String name;
}
I have removed services since this is not the point.
I've created CategoryDTO and SubCategoryDTO classes with the same fields as Category and SubCategory
The converter
class CategoryDTOConverter{
CategoryDTO convert(Category category,String fields){
CategoryDTO dto=new CategoryDTO();
dto.setName(category.getName());
if(StringUtils.isNotBlank(fields) && fields.contains("subCategories"){
category.getSubCategories().forEach(s->{
dto.getSubcategories().add(SubCategoryDTOConverter.convert(s));
}
}
}
}
I used com.cosium.spring.data.jpa.entity.graph.repository to create an EntityGraph from a list of attribute path
#Repository
interface CategoryRepository extends EntityGraphJpaRepository<Category, String>{
Optional<T> findById(String id,EntityGraph entityGraph);
}
Controller
#RestController
#CrossOrigin
#RequestMapping("/categories")
public class CategoryController {
#GetMapping(value = "/{id}")
public ResponseEntity<CategoryDTO> get(#PathVariable("id") String id, #RequestParam(value="fields",required=false) String fields ) throws Exception {
Optional<Category> categOpt=repository.findById(id,fields!=null?EntityGraphUtils.fromAttributePaths(fields):null);
if(categOpt.isEmpty())
throws new NotFoundException();
return ResponseEntity.ok(categoryDTOConverter.convert(categOpt.get(),fields);
}
}
This is a simple example to illustrate what I need to do
I don't want to load fields that clients doesn't want to use
How could I do this in a better way ?
Take a look at GraphQL since it is a perfect match for your use case. With GraphQL it is the client that decides which attributes it wants to receive by providing in the POST request body exactly which attributes are needed to be included in the response. This is way more manageable than trying to handle all this on your own.
Spring Boot recently added its own Spring GraphQL library, so it is quite simple to integrate it in your Spring Boot app.

Have one Rest repository json with everything and one with fields excluded

I have two entities: Book and Category and a repository for both. In the controller, I have set up the methods correctly as such:
#RequestMapping(value="/books", method = RequestMethod.GET)
#CrossOrigin
public #ResponseBody List<Book> bookListRest() {
return (List<Book>) bookRepository.findAll();
}
This obviously shows all books and every field in the entity that isn't #JsonIgnore'd. The problem is, I need to have:
One page with Book data (book name, author name, isbn..) without category
One page with Category data (Category name) without books
One page with Everything (book data along with categories where they belong in)
How can one accomplish this?
I somehow need to in a way ignore #jsonignore on some occasions. Should I make a new entity that extends say, Question and also make a repository for that? Surely that can't be the correct way to do this.
As khalid Ahmed Said you can use costum dtos or you can add Filters to ignore specific fields in Jackson. First, we need to define the filter on the java object:
#JsonFilter("myFilterBook")
public class Book{
...
}
#JsonFilter("myFilterCategory")
public class Category{
...
}
Before you return your ResponseBody you try to use ObjectMapper (Jackson):
The case of one page with Book data (book name, author name, isbn..) without category:
ObjectMapper mapper = new ObjectMapper();
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter
.serializeAllExcept("category");
FilterProvider filters = new SimpleFilterProvider()
.addFilter("myFilterBook", theFilter);
String dtoAsString = mapper.writer(filters).writeValueAsString(book);
You can do the same think by putting what you want o ignore for the other example.
And for more details to ignore field during marshalling with jackson you can check here
What about using DTOs data transfer objects
you can create multiple DTOs to use them in the response of your API
DTO is a pojo class that customize the returning data from your entity
public class BookWithoutCategoryDTO {
private String name;
private String authorName;
.....
/// and make setters and getters for them
}
public class BookWithCategoryDTO {
private String name;
private String authorName;
private String category;
.....
/// and make setters and getters for them
}
and create your custom mapper to convert from Book to BookDTO

LazyInitializationException with graphql-spring

I am currently in the middle of migrating my REST-Server to GraphQL (at least partly). Most of the work is done, but i stumbled upon this problem which i seem to be unable to solve: OneToMany relationships in a graphql query, with FetchType.LAZY.
I am using:
https://github.com/graphql-java/graphql-spring-boot
and
https://github.com/graphql-java/graphql-java-tools for the integration.
Here is an example:
Entities:
#Entity
class Show {
private Long id;
private String name;
#OneToMany(mappedBy = "show")
private List<Competition> competition;
}
#Entity
class Competition {
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
Schema:
type Show {
id: ID!
name: String!
competitions: [Competition]
}
type Competition {
id: ID!
name: String
}
extend type Query {
shows : [Show]
}
Resolver:
#Component
public class ShowResolver implements GraphQLQueryResolver {
#Autowired
private ShowRepository showRepository;
public List<Show> getShows() {
return ((List<Show>)showRepository.findAll());
}
}
If i now query the endpoint with this (shorthand) query:
{
shows {
id
name
competitions {
id
}
}
}
i get:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: Show.competitions, could not initialize proxy -
no Session
Now i know why this error happens and what it means, but i don't really know were to apply a fix for this. I don't want to make my entites to eagerly fetch all relations, because that would negate some of the advantages of GraphQL. Any ideas where i might need to look for a solution?
Thanks!
My prefered solution is to have the transaction open until the Servlet sends its response. With this small code change your LazyLoad will work right:
import javax.servlet.Filter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* Register the {#link OpenEntityManagerInViewFilter} so that the
* GraphQL-Servlet can handle lazy loads during execution.
*
* #return
*/
#Bean
public Filter OpenFilter() {
return new OpenEntityManagerInViewFilter();
}
}
I solved it and should have read the documentation of the graphql-java-tools library more carefully i suppose.
Beside the GraphQLQueryResolver which resolves the basic queries i also needed a GraphQLResolver<T> for my Showclass, which looks like this:
#Component
public class ShowResolver implements GraphQLResolver<Show> {
#Autowired
private CompetitionRepository competitionRepository;
public List<Competition> competitions(Show show) {
return ((List<Competition>)competitionRepository.findByShowId(show.getId()));
}
}
This tells the library how to resolve complex objects inside my Showclass and is only used if the initially query requests to include the Competitionobjects. Happy new Year!
EDIT 31.07.2019: I since stepped away from the solution below. Long running transactions are seldom a good idea and in this case it can cause problems once you scale your application. We started to implement DataLoaders to batch queries in an async matter. The long running transactions in combination with the async nature of the DataLoaders can lead to deadlocks: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (above and below for more information). I will not remove the solution below, because it might still be good starting point for smaller applications and/or applications which will not need any batched queries, but please keep this comment in mind when doing so.
EDIT: As requested here is another solution using a custom execution strategy. I am using graphql-spring-boot-starter and graphql-java-tools:
Create a Bean of type ExecutionStrategy that handles the transaction, like this:
#Service(GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY)
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {
#Override
#Transactional
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
return super.execute(executionContext, parameters);
}
}
This puts the whole execution of the query inside the same transaction. I don't know if this is the most optimal solution, and it also already has some drawbacks in regards to error handling, but you don't need to define a type resolver that way.
Notice that if this is the only ExecutionStrategy Bean present, this will also be used for mutations, contrary to what the Bean name might suggest. See https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v11.1.0/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/spring/web/boot/GraphQLWebAutoConfiguration.java#L161-L166 for reference. To avoid this define another ExecutionStrategy to be used for mutations:
#Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
return new AsyncSerialExecutionStrategy();
}
For anyone confused about the accepted answer then you need to change the java entities to include a bidirectional relationship and ensure you use the helper methods to add a Competition otherwise its easy to forget to set the relationship up correctly.
#Entity
class Show {
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "show")
private List<Competition> competition;
public void addCompetition(Competition c) {
c.setShow(this);
competition.add(c);
}
}
#Entity
class Competition {
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
The general intuition behind the accepted answer is:
The graphql resolver ShowResolver will open a transaction to get the list of shows but then it will close the transaction once its done doing that.
Then the nested graphql query for competitions will attempt to call getCompetition() on each Show instance retrieved from the previous query which will throw a LazyInitializationException because the transaction has been closed.
{
shows {
id
name
competitions {
id
}
}
}
The accepted answer is essentially
bypassing retrieving the list of competitions through the OneToMany relationship and instead creates a new query in a new transaction which eliminates the problem.
Not sure if this is a hack but #Transactional on resolvers doesn't work for me although the logic of doing that does make some sense but I am clearly not understanding the root cause.
For me using AsyncTransactionalExecutionStrategy worked incorrectly with exceptions. E.g. lazy init or app-level exception triggered transaction to rollback-only status. Spring transaction mechanism then threw on rollback-only transaction at the boundary of strategy execute, causing HttpRequestHandlerImpl to return 400 empty response. See https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250 and https://github.com/graphql-java/graphql-java/issues/1652 for more details.
What worked for me was using Instrumentation to wrap the whole operation in a transaction: https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a
I am assuming that whenever you fetch an object of Show, you want all the associated Competition of the Show object.
By default the fetch type for all collections type in an entity is LAZY. You can specify the EAGER type to make sure hibernate fetches the collection.
In your Show class you can change the fetchType to EAGER.
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
You just need to annotate your resolver classes with #Transactional. Then, entities returned from repositories will be able to lazily fetch data.

Sort feature in association endpoint in Spring Data Rest

I have the following two resources, and their association;
#Table(name = "Item")
#Data
#Entity
public class Item {
#ManyToOne
#JoinColumn(name = "fk_wrapper")
private Wrapper wrapper;
#Id
#GeneratedValue
private String id;
private Integer someValue;
}
and;
#Table(name = "Wrapper")
#Data
#Entity
public class Wrapper {
#Id
#GeneratedValue
private String id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_wrapper")
private List<Item> items;
private String someField;
}
Then, first, I create a Wrapper;
POST http://localhost:8080/wrappers/
{
"someField": "asd"
}
http://localhost:8080/wrappers/1 created, then I create two Item's, linked to this Wrapper;
POST http://localhost:8080/items/
{
"someValue": "5",
"wrapper": "http://localhost:8080/wrappers/1"
}
&
POST http://localhost:8080/items/
{
"someValue": "7",
"wrapper": "http://localhost:8080/wrappers/1"
}
After all this, when I call the endpoint http://localhost:8080/wrappers/1/items, I get the list of these two items, as expected, but what the trouble is that, I cannot seem to have a sorting feature on this endpoint. I seem to be able to sort in http://localhost:8080/items endpoint, but while fetching with association, there doesn't seem to be a sorting feature. Is this lack of sorting is intended, or am I lacking some configuration?
P.S. when I create a custom search method, for example;
#RepositoryRestResource
public interface ItemRepository extends JpaRepository<Item, String> {
List<Item> findByWrapper_Id(#Param("id") String id, Sort sort);
}
Then I can use the sorting with http://localhost:8080/items/search/findByWrapper_Id endpoint, but too ugly imo, considering there is already an auto-generated endpoint.
Spring Data Rest doesn't support sorting on the associations.
You seem to have already found the best way to do what you need, according to the Spring Data Rest team - create a query for fetching the data you need. That will indeed support both pagination and sorting.
The reason why it's not supported has to do with the time when the queries are made to fetch the main resource (before the association endpoints are built) and the facts that the association endpoint makes use of the the loaded entity associations directly and that for supporting sort, a new query would need to be made anyway.
More detailed information can be found here:
https://jira.spring.io/browse/DATAREST-725?focusedCommentId=122244&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-122244
Cheers!

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

Resources