I have simple scenario where there is relation between User and Skill,
means one user many skills, so I tried with:
User
#Data
#NoArgsConstructor
#Entity
#EqualsAndHashCode
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy = "user")
private List<Skill> skills;
}
Skill
#Data
#NoArgsConstructor
#Entity
#EqualsAndHashCode
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String skillTitle;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
}
UserRepository
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
List<User> findByName(#Param("name") String name);
}
SkillRepository
#RepositoryRestResource(collectionResourceRel = "skills", path = "skills")
public interface SkillRepository extends CrudRepository<Skill, Long>{
}
with all above I'm able to get response at for example url http://localhost:8085/users/1
{
"name": "Root",
"_links": {
"self": {
"href": "http://localhost:8085/users/1"
},
"user": {
"href": "http://localhost:8085/users/1"
},
"skills": {
"href": "http://localhost:8085/users/1/skills"
}
}
}
not the issue is I'm not figuring out why list of skills is not fetched, why only this is fetched
"skills": {
"href": "http://localhost:8085/users/1/skills"
}
not a full list of skills related to user/1.
UPDATE
Added projection as suggested:
UserProjection.java
#Projection(name = "inlineData", types=User.class)
public interface UserProjection {
String getName();
List<Skill> getSkills();
}
UserRepository.java is
#RepositoryRestResource(collectionResourceRel = "users", path = "users", excerptProjection = UserProjection.class)
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
List<User> findByName(#Param("name") String name);
}
response is:
{
"name": "Root",
"_links": {
"self": {
"href": "http://localhost:8085/users/1"
},
"user": {
"href": "http://localhost:8085/users/1{?projection}",
"templated": true
},
"skills": {
"href": "http://localhost:8085/users/1/skills"
}
}
}
The response is correct, it works as intended. #RepositoryRestResource follows HATEOAS principles. Spring documentation explains it as follows:
5.1.3. Resource Discoverability
A core principle of HATEOAS is that resources should be discoverable
through the publication of links that point to the available
resources ...
By issuing a request to the root URL ... the client can extract, from
the returned JSON object, a set of links that represent the next level
of resources that are available to the client ...
You get links that represent resources. To retrieve specific resources you should call corresponding URL. Your response for User 1 means that if you want to get Skills of User 1 you should call URL "http://localhost:8085/users/1/skills".
It is easier to understand it if you imagine that you have an HTML page that displays properties of User 1. This page does not directly display Skills, instead this page contains a link to Skills page. Only if a user clicks on this link the Skills page will be loaded.
It is important that you understand HATEOAS well.
Of course there can be cases when HATEOAS is not the best choice. But here we are not discussing HATEOAS, but explaining what is the idea behind this implementation of Spring. This approach can really be helpful in many cases. When you have 2 entities with 1-2 attributes, you may consider such approach as overkill. But if you have 30 - 50 entities, each with 3 - 5 relations, each relation containing 50 - 100 other entities, it can be quite hard to deal with such data model. And HATEOAS can make it much easier. With this approach you are just navigating these relations: load one entity, select needed relation, load entities on this relation, select needed entity, in this entity select needed relation, load this relation, or navigate back to its parent entity via parent relation, etc.
Related
I'm working on a Spring Data Rest based Spring Boot service whose data model is similar to the one in the tutorial:
https://www.baeldung.com/spring-data-rest-relationships
(In defining the entities, I'm using Lombok annotations):
#Data
#NoArgsConstructor
#Entity
#Table(name = "cale")
public class Book {
#Id
#GeneratedValue
private long id;
#Column(nullable=false)
private String title;
#ManyToOne
private Library library;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "library")
public class Library {
#Id
#GeneratedValue
private long id;
//...
}
invoking the endpoint /books I get the following json:
{
"_embedded": {
"books": [
{
"id": 22,
"title": "The title of the book",
"_links": {
"self": {
"href": "http://192.168.33.20:8080/books/22"
},
"book": {
"href": "http://192.168.33.20:8080/books/22"
},
"library": {
"href": "http://192.168.33.20:8080/books/22/library"
}
}
},
.
.
.
in HAL style, the only reference to the library linked to a given book is through an URL like http://192.168.33.20:8080/books/22/library
In order to get the id of the library associated to book 22, I have to perform a second GET call to the URL, which is inefficient in many cases.
Moreover, this makes it very hard to implement queries like "GET all books associated to the library whose id is 101".
Is there a way to let Spring Data Rest include also the id of the associated entity into the returned json? Something like:
{
"_embedded": {
"books": [
{
"id": 22,
"title": "The title of the book",
"library_id": 101,
"_links": {
.
.
.
}
},
.
.
You can create a projection and use it by default with an excerpt.
To define the projection :
#Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
#Value("#{target.id}")
long getId();
String getTitle();
#Value("#{target.getLibrary().getId()}")
int getLibraryId();
}
And call :
http://192.168.33.20:8080/books/22?projection=customBook
To use this projection by default configure your repo :
#RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository<Book, Long> {}
We were given an assignment to recreate a simple version of the Twitter API in Spring using Mapstruct.
We are returning a List<UserDto> that should return the field username from the embedded object Credentials.
We mapped this as follows:
#Mapper(componentModel = "spring", uses = {ProfileMapper.class, CredentialMapper.class})
public interface UserMapper {
User dtoToEntity(CreateUserDto createUserDto);
#Mapping(target = "username", source = "credentials.username")
List<UserDto> entitiesToDtos(List<User> users);
}
Our UserDto is specified like this:
#NoArgsConstructor
#Data
public class UserDto {
private ProfileDto profile;
private Timestamp joined;
private String username;
}
Our User entity has an embedded object named credentials, where the username and password of the user are stored in String format (I know this is dumb, this is just an assignment).
#NoArgsConstructor
#Entity
#Data
#Table(name="users")
public class User {
#Id
#GeneratedValue
private Long id;
#CreationTimestamp
private Timestamp joined;
private boolean deleted;
#Embedded
private Credential credentials;
#Embedded
private Profile profile;
Long story short, when we GET all users, we should receive this (these are fake names and numbers):
{
"profile": {
"firstName": "Chanda",
"lastName": "Hackett",
"email": "chandahackett#gmail.com",
"phone": "313-574-1401"
},
"joined": "2021-03-17T21:15:35.289+00:00",
"username": "chandahackett"
}
But instead, we receive a null value for username:
{
"profile": {
"firstName": "Chanda",
"lastName": "Hackett",
"email": "chandahackett#gmail.com",
"phone": "313-574-1401"
},
"joined": "2021-03-17T21:15:35.289+00:00",
"username": null
}
I know the value username in credentials exists, as it exists in the table it is stored:
And it is accessible because other methods that call user.getCredentials().getUsername() return the right username.
I have tried pretty much everything. I have run mvn clean install, renamed variables. I'm out of ideas. Any help would be appreciated.
The way you're trying to use #Mapping on a collection-mapping method isn't supported at the moment. You need to declare an explicit mapping from User to UserDto, and apply the annotation on it instead:
#Mapper(componentModel = "spring", uses = {ProfileMapper.class, CredentialMapper.class})
public interface UserMapper {
User dtoToEntity(CreateUserDto createUserDto);
#Mapping(target = "username", source = "credentials.username")
UserDto entityToDto(User user);
List<UserDto> entitiesToDtos(List<User> users);
}
I have problems with the relationship of one to many, to relate I just want to use the user ID I do not want to create a new one, I put my code
#PostMapping("/createFavorite")
public ResponseEntity<Favorite> createFavorite(#RequestBody Favorite favorite) {
return new ResponseEntity<Favorite>(userServiceImpl.createFavorite(favorite), HttpStatus.OK);
}
my entity Favorite is
public class Favorite implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "element")
private String element;
#ManyToOne
#JoinColumn(name = "user_fk")
User user;
in swagger i get this
{
"element": "string",
"user": {
"email": "string",
"id": 0,
"life": "suspend",
"name": "string",
"password": "string"
}
}
i do not want to create new user,
The user is already created, I just need to use your ID to reference it
You need to create a new user or retrieve any existing user and set in favorite object. If in favorite.user.id is present, it won't create a new user.
Even you should set Cascade to CascadeType.DETACH to avoid creation or updation of User object from favorite object. It will throw error if favorite.user.id is null instead of creating a new user object.
#ManyToOne(cascade = CascadeType.DETACH)
#JoinColumn(name = "user_fk")
User user;
Your entity structure is fine. You have to retrieve the user from UserRepository and assign it in the Favorite entity. IMHO, please introduce FavoriteDTO class. So entity models will not be exposed.
FavoriteDTO.java
public class FavoriteDTO implements Serializable {
private long id;
private String element;
long userId;
//getters and setters
}
Your service implementation should check the user availability and then assign it to the Favorite entity.
ServiceImpl.java
public void createFavorite(FavoriteDTO favoriteDTO) {
Optional<User> optional = userRepository.findById(favoriteDTO.getUserId());
if(optional.isPresent()) {
Favorite favorite = new Favorite();
favorite.setUser(optional.get());
favoriteRepository.save(favorite);
}
}
Your updated swagger request json like below
{
"element": "string",
"userId": long
}
You need to retrieve your existing user and put it there as the favorite.user object (assign it). That way, it will work.
I am not very clear with your swagger specification. Are you using some kind of json schema?
hi newbie to Hibernate,
My entity classes
#Entity
public class User {
#Id
#GeneratedValue//How to restrcit by passing id from response
#JsonIgnore
private Integer userId;
#OneToMany(mappedBy = "user")
private List<PostEntity> postEntity;
}
#Entity
#Table(name="post")
public class PostEntity {
#Id
#GeneratedValue
#JsonIgnore
#ApiModelProperty(required=false)
private Integer id;
#ManyToOne(fetch=FetchType.LAZY)
private User user;
}
When I fetch User its populating Post entity as well.
URI: http://localhost:8080/jpa/users
[
{
"name": "Abdul",
"birthDate": "2018-07-25T01:29:51.895+0000",
"postEntity": [
{
"description": "My Fourth Post"
},
{
"description": "My First Post"
},
{
"description": "My Second Post"
}
]
},
{
"name": "Anji",
"birthDate": "2018-07-25T01:29:51.903+0000",
"postEntity": []
},
{
"name": "Naren",
"birthDate": "2018-07-25T01:29:51.903+0000",
"postEntity": []
}
]
but this is not the case in reverse. When I fetch post its skipping User entity.
URI: localhost:8080/jpa/users/101/posts/11001
Response:
{
"description": "My First Post"
}
Why its not populating user information in the above JSON response.
Fetching Methods:
User:
#GetMapping("/jpa/users")
public List<User> retAll(){
return userRepository.findAll();
}
Post:
#GetMapping("/jpa/users/{uid}/posts/{pid}")
public Resource<PostEntity> postE(#PathVariable int uid,#PathVariable int pid) {
Optional<PostEntity> post = postRepository.findById(pid);
if (!post.isPresent()) {
throw new UserNotFoundException("POst");
}
PostEntity ePost = post.get();
Resource<PostEntity> resource = new Resource<PostEntity>(ePost);
return resource;
}
Please help.
That is actually the intended way REST is supposed to work.
GET at /users : all users
GET at /users/1 : information of user 1 and all its children
GET at /users/1/posts : all posts of user 1
GET at /users/1/posts/10 : information of post 10 and all its children from user 1
As you're calling /users/101/posts/11001, the endpoint will give you the information of one post (id 11001) from one user (id 101).
There are two common ways to get the parent information:
The fastest way would be to just call /users and filter for your desired post in the frontend.
The right way would be changing the model of your post (PostEntity.java) to contain its "parent" User object, so when you make a REST call for a post, the user object gets populated.
Further reading:
https://softwareengineering.stackexchange.com/questions/274998/nested-rest-urls-and-parent-id-which-is-better-design
Maybe it's a good idea to read some REST best practices:
https://hackernoon.com/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9
Try to use FetchType
#Entity
public class User {
#Id
#GeneratedValue//How to restrcit by passing id from response
#JsonIgnore
private Integer userId;
#OneToMany(mappedBy = "user", fetch=FetchType.EAGER)
private List<PostEntity> postEntity;
}
Beware of the performance.
I have two entities, Company and Job, with an OneToMany bidirectional relationship. My problem is that i can't lazy load the Company's List<Job> jobs.
For example when i do:
GET /api/companies/1 this is the JSON response:
{
"id": 1,
"name": "foo",
...
"_embedded": {
"jobs": [
{...},
...
{...}
],
"employees": [
{...},
{...}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/companies/1"
},
"jobs": {
"href": "http://localhost:8080/api/companies/1/jobs"
},
"employees": {
"href": "http://localhost:8080/api/companies/1/employees"
}
}
}
I don't want to have the _embedded since i didn't set the FetchType=EAGER.
Here are my models:
Company.java
#Entity
public class Company {
#Column(nullable = false, unique = true)
private String name;
#OneToMany(mappedBy = "company", fetch = FetchType.LAZY)
private List<Job> jobs;
...
public Company() {
}
...
}
Job.java
#Entity
public class Job {
#Column(nullable = false)
public String title;
#Column(length = 10000)
public String description;
#ManyToOne(fetch=FetchType.LAZY)
private Company company;
...
public Job() {
}
...
}
As you can see the same thing happens for other OneToMany relationships (employees). Can i avoid returning the whole list of job openings or employees every time?
EDIT: from the Job side the lazy load works fine! I don't get in the response the company that is related with a Job. I have to explicitly do /api/jobs/123/company in order to get the company.
EDIT2: Projections only work for collections. In this case it's not what i need. Excerpts could work, but i want to avoid them. I don't want to explicilty do /api/companies/1?projection=MyProjection since i won't use more than one. I want to change the default behavior, just like the projections do in collections.
EDIT3: i tried this
#RestResource(exported = false)
#OneToMany(mappedBy = "company")
private List<Job> jobs;
and i get the error Detected multiple association links with same relation type! Disambiguate association.
it's really annoying. I just need to get rid of _embedded. Anything?
You can use Entity Graph.Entity graphs are used to override at runtime the fetch settings of attribute mappings.For example
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
From Spring Data Jpa Documentation "4.3.10. Configuring Fetch- and LoadGraphs"
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
In addition;