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.
Related
I'm working on a Spring/React project where I have about 10 tables, which hold several relations.
But I got a really annoying situation where I just can't find the right solution. I have a few tables which hold one2many relations:
Product.category_id --> Category.id
Product.manufacturer_id --> Manufacturer.id
Product entity:
#ManyToOne
#JoinColumn(name="category_id", referencedColumnName = "id", nullable = true)
private Category category;
#ManyToOne
#JoinColumn(name="manufacturer_id", referencedColumnName = "id")
private Manufacturer manufacturer;
Product Controller:
#PostMapping(value="/admin/product")
public ResponseEntity<Object> createProduct(#RequestBody Product product) {
System.out.println(product.getCategory());
System.out.println(product.getManufacturer());
productRepository.save(product);
URI location;
return new ResponseEntity<>("Toegevoegd", HttpStatus.CREATED);
}
#PutMapping(value = "/admin/product/{id}")
public Product updateProduct(#RequestBody Product product, #PathVariable Long id) {
return productRepository.findById(id)
.map(updateProduct -> {
updateProduct.setCategory(product.getCategory());
updateProduct.setManufacturer(product.getManufacturer());
updateProduct.setName(product.getName());
updateProduct.setPrice(product.getPrice());
updateProduct.setTaste(product.getTaste());
updateProduct.setImagePath(product.getImagePath());
updateProduct.setStock(product.getStock());
updateProduct.setDescription(product.getDescription());
updateProduct.setType(product.getType());
return productRepository.save(updateProduct);
})
.orElseGet(() -> {
return productRepository.save(product);
});
}
Category entity:
#OneToMany(mappedBy = "category")
#JsonIgnore
private Set<Product> Product;
The relation works when i get the overview of products, i get "1 - test", but when I want to send a POST request, both manufacturer/category's are always null. The output I send with Postman:
{
"categoryId": "999",
"name":"Test",
"taste":"sdads",
"price":"1",
"stock": "123",
"manufacturerId": "1",
"description" : "213",
"discount" : "0"
}
Because of the camelCase convention it has to be sent this way from the front-end. The annoying part is that, when I didn't add a relation between product-category / product-manufacturer and I used:
#Column(name="manufacturer_id")
private long manufacturerId;
#Column(name="category_id")
private long categoryId;
It did work, I could Post/Put data and it was saved to the database. When I use this code along with the one2many mapping, it says 'category' was already defined (or smt). A teacher said the framework would automatically manage it and said the relationships were ok. Well, its not working.
What is the correct way of saving data WITH a relationship? All the other non-relation columns have no issues updating/inserting.
Well when you send POST query with your JSON, Jackson converts that JSON to Product object. And since Product has not fields like manufacturerId or categoryId, Jackson ignores them.
And no wonder category and manufacturer fields are null - JSON has no such fields.
You should make a class ProductDto with same fields as Product except for it should have manufacturerId and categoryId instead of manufacturer and. And you should convert ProductDto to Product manually in your code, filling category with categoryId and manufacturer with manufacturerId. This is the right way.
Another option is to add full Manufacturer and Categoty object to your JSON
{
"category": {
"id": "999"
},
"name":"Test",
"taste":"sdads",
"price":"1",
"stock": "123",
"manufacturer": {
"id": "1"
},
"description" : "213",
"discount" : "0"
}
Should work, but I don't recomend it
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?
Working on this 'twitter' application where a user can have posts #OneToMany and can have followers #ManyToMany.
While retrieving a user all it's posts and followers get retrieved as well.
This is all correct but it's also retrieving every 'poster' for each post (which is the user itself) and for each follower, all it's posts and followers.
I can't figure out how to limit this to the user itself.
User
#Entity
#Table(name = "User")
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
#NamedQuery(
name = "User.auth",
query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"
)
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
#ManyToMany
#OneToMany(mappedBy = "poster", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
#JoinTable(name = "Followers",
joinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
},
inverseJoinColumns = {
#JoinColumn(name = "FOLLOWER_ID", referencedColumnName = "ID")
}
)
private List<User> followers = new ArrayList<>();
.... constructor, getters and setters
Post
#Entity
#Table(name = "Post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
#ManyToOne
private User poster;
.... constructor, getters and setters
Outcome I get vs what I want
{
"id": 1,
"username": "jim",
"posts": [
{
"id": 1,
"content": "Post 1 by jim",
"poster": {
// ^ this is the user itself (I don't need this one)
"id": 1,
"username": "jim",
"posts": [
// keeps recurse
]
}
}
],
"followers": [
{
"id": 2,
"username": "follower1",
"posts": [
{
"id": 4,
"content": "Post 2 by follower 1",
"poster": {
// ^ this is the follower itself (I don't need this one)
"id": 2,
"username": "follower1",
"posts": [
// Same issue
]
}
}
],
"followers": [], // <-- I don't need this one either
}
]
}
Well it's pretty clear that fetching one user fill keeps fetching all it's relations which are recursive.
Is this a designer's fault or can this be ignored/limited?
Note: I am using Gson to serialise objects to JSON format
Update
Tried to use:
#ManyToOne(fetch = FetchType.LAZY)
private User poster;
Which works but still gets the following extra prop in JSONso not sure if this is a neath solution:
"_persistence_poster_vh": {
"sourceAttributeName": "poster",
"isInstantiated": false,
"row": {
"Post.ID": 3,
"Post.CONTENT": "Post 3 by jim",
"Post.DATETIME": "2018-01-22",
"Post.POSTER_ID": 1
},
"isCoordinatedWithProperty": false
}
And
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
...
)
private List<User> followers = new ArrayList<>();
Which still returns all followers (which I want!) I just don't want the followers.followers and followers.posts..
Best guess: it’s not actually fetching these objects until you try to dereference them.
Be default, JPA will eager fetch #OneToOne and #OneToMany relations, but not #ManyToOne or #ManyToMany. What happens is that when you reference these fields, it will then go and fetch the actual contents of the list.
How can you tell this is happening? Check the list’s class using getFollowers().getClass()
What you see won’t be a LinkedList or an ArrayList but a class from your JPA provider, probably with “Lazy” somewhere in the name. When you call Size or Get on the list, it will perform the fetch.
You can set OneToOne and OneToMany relations to be lazy as well, and use EntityGraphs to determine what entities you want to eagerly fetch as well. JPA has a number of gotchas like this.
I’ve seen GSON mentioned, and just a warning: it won’t be aware of the lazy loading lists, so you MUST tell It to avoid the properties you don’t want it to follow.
Typically with JSON marshaling, you’ll want it to ignore the parent object, so in Post, User should be ignored for example. Additionally links to same types should typically be ignored (followers) or else mapped specially, such that it doesn’t Marshall the entire object, but only produces an array of usernames. You can tell it to ignore the actual followers field, and have it marshal a getter which returns an array of usernames to implement this.
You can specify fetch=FetchType.LAZY in the annotation you don't want to fetch immediately. The downside is, that if you need the data later you have to access it in the scope of the still open session.
There are two ways to handle this -
You can either use #JsonIgnoreProperties(ignoreUnknown=true) anotation on attributes you want to skip while serializing the object.
Or you change your FetchType to FetchType.LAZY so that you can get the required data on need basis while preparing your JSON , rather than getting all records at once.
Lets assume I have the following JPA entities:
#MappedSuperclass
public abstract class BaseForumPersistable {
#Id
Long id;
String title;
Date creationDate;
#ManyToOne
User user;
//getters, setter
}
#Entity
public class ThematicArea() {
#OneToMany(mappedBy="thematicArea")
List<Topic> topics;
//getters, setters
}
public class Topic() {
String status;
boolean isSticky;
#ManyToOne
ThematicArea thematicArea;
#OneToMany
List<Post> posts
//getters, setters
}
I also use these entities for my REST Controllers that handle POST requests. So for instance for Topic I have an /api/topics endpoint. When I send something like this as a JSON object
{
"user": {
"id": 3,
"role": "Admin"
},
"thematicArea": {
"id": 1
},
"title": "asdf",
"status": "Active"
}
It fails to create the Thematic Area, although it perfectly creates the User entity. The controller signature is as follows:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> create(#RequestBody final Topic entity)
So when I use the debugger with a breakpoint, the ThematicArea entity is not even de-serialized.
Moreover if I send an object like this:
{
"user": {
"id": 3,
"role": "Admin"
},
"thematicArea": {
"id": 1,
"title": "topic_title"
},
"title": "asdf",
"status": "Active"
}
Which now also includes a title field in the ThematicArea object the two title fields get mixed up. This leads me to believe that it's an issue of de-serializing. Any ideas how i can fix this.
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;