How to test with #JSONManagedReference the way I want it? - spring

I want to show the "parent" data in the "child" entity response in a #manyToOne relationship.
#Entity
public class Parent {
#JsonBackReference(value = "parent-children")
#OneToMany(mappedBy = "parent", targetEntity = Child.class)
private List<Children> children;
}
#Entity
public class Child {
#JsonManagedReference(value = "parent-children")
#ManyToOne
private Parent parent;
}
The response object for such a relationship is amazing, exactly what I want
GET web.site/children/all
[
{
"id": 1,
"parent": {
"id": 1,
"other": "data",
},
}
]
But when I run tests, the test runner can't compile! Searching for this error on Google gets me to several articles that say that I have the relationship the wrong way around.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'parent-children': back reference type (java.util.List) not compatible with managed type (website.entities.Child)
Switching the types around causes JSON results I don't want
GET web.site/children/all
[
{
"id": 1,
}
]

You could try using #JsonIgnoreProperties like this:
#Entity
public class Parent {
#JsonIgnoreProperties("parent")
#OneToMany(mappedBy = "account", targetEntity = Child.class)
private List<Children> children;
}
#Entity
public class Child {
#JsonIgnoreProperties("children")
#ManyToOne
private Parent parent;
}
Some notes:
1) You are using mappedBy="account", why don't you use mappedBy="parent" ?
2) By convention, you should use private List<Children> childrens; in plural

Related

How can we aggregate results in a list for one to many relationship in spring jpa

I have 2 tables with structures like this :
Table 1: user
id, // Primary key
name,
email
Table 2: user_social
user_id, // Foreign key to user table
social_channel,
social_link
The data is as follows:
user:
1, "Ram", "ram#gmail.com"
user_social:
1, "FB", "fb.com/ram"
1, "INSTA", "insta.com/ram"
And the respective JPA entities classes:
User.java
#Entity
#Table(name = "user")
public class User {
#Id
UUID id;
String name;
String email;
#OneToMany(mappedBy = "id.userObj")
Set<UserSocial> userSocial = new HashSet<>();
}
UserSocial.java
#Entity
#Table(name = "user_social")
public class UserSocial {
// there is no primary key to the table and the whole row is unique
#EmbeddedId
UserSocialId id;
#Embeddable
public static class UserSocialId {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
User userObj;
String social_link;
String social_channel
}
}
Now when I try to fetch all the user details, I get duplicate results like this:
[
{
"id": 1,
"name": "Ram",
"email": "ram#gmail.com",
"userSocial": [
{ "social_link": "fb.com/ram", "social_channel": "FB" },
{ "social_link": "insta.com/ram", "social_channel": "INSTA" }
]
},
{
"id": 1,
"name": "Ram",
"email": "ram#gmail.com",
"userSocial": [
{ "social_link": "fb.com/ram", "social_channel": "FB" },
{ "social_link": "insta.com/ram", "social_channel": "INSTA" }
]
}
]
I believe this is because JPA makes a join query and since the user_social table has 2 rows in it, it duplicates the result for it. Is there a way where we could aggregate the user_social results in a list and thus have a single JSON element in response?
EDIT:
I am using an interface interface UserRepository extends JpaRepository<User, UUID> 's method findAll method to fetch results.

Expose Association in Entity via Rest in Spring Boot

I'm new in Spring Boot and a would like to add an association to the HTTP-Request.
Example:
// Transaction.java
#Entity
public class Transaction extends RepresentationModel<Transaction> {
// id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Tag> tags;
}
// Tag.java
#Entity
public class Tag extends RepresentationModel<Transaction> {
// id;
// name..., and so on
}
Now I have two Repositories:
public interface TagRepository extends JpaRepository<Tag, Long> {
}
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
}
When calling GET http://localhost:8080/transactions
I get:
{
"_embedded": {
"transactions": [
{
"date": "2021-01-01T01:01:11.000+00:00",
"and so on": "more fields" // but no "tags": [...]
"_links": {
"self": {"href": "http://localhost:8080/transactions/1"},
"tags": {"href": "http://localhost:8080/transactions/1/tags"}
}
}
]
},
"_links": {...},
"page": {...}
}
Is there any way to Modify the default Spring Endpoints to return an array of tags?
I would like to bypass create a custom Controller for this, because i need some parameters and filter methods.
The reason I need this function is, that I have a page which will load about 1k transactions and I don't want do make n+1 requests on GET /transactions/n/tags
Thanks for Help.
You need to add the join table annotation like this:
// Transaction.java
#Entity
public class Transaction extends RepresentationModel<Transaction> {
// id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "tag_transaction",
joinColumns = #JoinColumn(name = "transaction_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "id"))
private List<Tag> tags;
}
// Tag.java
#Entity
public class Tag extends RepresentationModel<Transaction> {
// id;
// name..., and so on
}

ManyToOne saves null reference

I am learning Springboot/Hibernate and am having some trouble with ManyToOne notation.
I have three models: Order, OrderDetail, and Product. Each Order has several OrderDetails (OneToMany, this works fine) and each OrderDetail holds an id of one Product (products can be repeated, therefore it should be ManyToOne).
What happens is that everytime I try to save an order the details get created and saved but the products (which already exist in my database) within each detail are being set null. I have tried everything I found on the internet for the past 4 hours but haven't found what's causing my issue yet.
Minimal code, let me know if you need more details:
#RestController
public class OrderController {
#Autowired
OrderRepository orderRepository;
#PostMapping("/orders")
public Order addOrder(#RequestBody Order order)
{
return orderRepository.save(order);
}
}
#Entity
#Table(name="orders")
public class Order {
#Id
#GeneratedValue
#Column(name = "order_id")
private Long id;
//...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id")
#Getter
#Setter
private List<OrderDetail> orderDetailList;
//...
^This works just fine
#Entity
#Table(name="order_details")
public class OrderDetail {
#Id
#GeneratedValue
#Column(name = "order_detail_id")
private Long id;
//...
#ManyToOne(/*optional = false, */cascade = CascadeType.ALL, targetEntity=Product.class)
#JoinColumn(name = "product_id")
#Getter
#Setter
private Product product;
//...
^I have commented optional=false otherwise I get a 500 error because of the product being null
#Entity
#Table(name="products")
public class Product {
#Id
#GeneratedValue
#Column(name="product_id")
private Long id;
//...
// #OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
// private List<OrderDetail> orderDetailList;
//...
}
^ I have tried adding or not adding this side of the relationship, none works; I think I should not need it anyways because I interact with my detail's product and not with my product's details.
And here is my Postman POST Request to /orders:
{
"address": "Dorton Road 80",
"email": "tsayb#opera.com",
"tel": "(0351) 48158101",
"schedule": "21:00",
"orderDetailList": [
{ "product_id": 1,
"quantity": 1 },
{ "product_id": 2,
"quantity": 1 }]
}
Which returns
{
"address": "Dorton Road 80",
"email": "tsayb#opera.com",
"tel": "(0351) 48158101",
"schedule": "21:00",
"orderDetailList": [
{
"quantity": 1,
"product": null
},
{
"quantity": 1,
"product": null
}
]
}
Note that products are null, but my products id 1 and 2 do exist in the products crud. Why are them not being saved(or returned) ?
Thank you very much for your time.
each OrderDetail holds an id of one Product (products can be repeated, therefore it should be ManyToOne).
I think you are right. Many orderDetail to One product.
Could you check in the database to see if the product_id is inserted into the order_details table? Note that "saved, not fetched" and "not saved, not fetched" are two problems.
Could you add fetch = FetchType.EAGER in the #ManyToOne and #OneToMany annotations to make sure that they are fetched eagerly?
Besides, it is not relevant to your question, but plural forms are conventionally forbidden as table names such as products.

Spring boot JSON return infinite nested objects

I have the following in my code:
CompanyEntity
#Entity
#Table(name = "company")
public class Company{
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
#JsonUnwrapped
private Set<User> users;
}
UserEntity
#Entity
#Table(name="user")
public class User{
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name="company_id")
private Company company;
}
CompanyController
#GetMapping("/company")
public ResponseEntity<Object> getAllCompanies(){
List<Company> allCompanies = companyService.findAll();
return ResponseEntity.ok(allCompanies);
}
problem is when i call /company in the browser i am getting the users object including the company object. something like this
[
{
"id": 1,
"name": "company",
"users": [
{
"id": 14,
"firstName": "Yamen",
"lastName": "Nassif",
"company": {
"id": 1,
"name": "company",
"users": [
{
"id": 14,
"firstName": "Yamen",
"lastName": "Nassif",
"company": {
"id": 1,
"name": "company",
"users": [
...
same goes when i getAllUsers companies and users are also exanding.
my database looks just fine.
and its endless and of course Stackoverflow error is in the console. How can i fix this ?
You have this error because of the infinite recursion.
Company has a link on User and User has a link on Company.
You have at least two options:
use #JsonManagedReference and #JsonBackReference annotation on the relation fields.
create a pair of DTOs and fill them manually with data from you entities.
e.g.
#GetMapping("/company")
public ResponseEntity<Object> getAllCompanies() {
List<Company> allCompanies = companyService.findAll();
List<CompanyDto> allCompanyDtoList = convertToCompanyDtoList(allCompanies);
return ResponseEntity.ok(allCompanyDtoList );
}
Personally, I'd prefer the second option, since returning Entities is NOT a good practice.
You can use #JsonIgnore annotation to prevent this type of behavior. This usually happens with bidirectional mapping within your entities. It is caused by infinite recursion.
#Entity
#Table(name="user")
public class User{
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name="company_id")
#JsonIgnore
private Company company;
}

Bidirectional mapping does not pick up parent

I have a bidirectional relationship between two entities. Here are the two classes, getters/setters omitted:
#Entity
#Table(name = "strategy")
public class Strategy {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy = "strategy", cascade = { CascadeType.ALL })
private List<StrategyDetail> details;
}
And
public class StrategyDetail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String content;
#ManyToOne()
private Strategy strategy;
}
I then have a repository and controller over this, but nothing worth mentionning (my repository only extends the JpaRepository and the controller has a get/post mapping).
When I try to create a strategy, I send this JSON:
{
"name": "my strat",
"details": [{
"content": "my content"
}]
}
and the response is this:
{
"id": 1,
"name": "my strat",
"details": [
{
"id": 2,
"content": "my content",
"strategy": null
}
]
}
I don't get why the strategy is null here, the mapping is fairly obvious. When I then try to get the strategy, the answer is this:
[
{
"id": 1,
"name": "my strat",
"details": []
}
]
Which I guess is working as intended (foreign key failure so the detail has not been saved?).
What should I change to make it work (meaning having access to the strategy inside the strategyDetail)? Even better I think would be having the strategy id rather than the whole strategy.
I've tried removing the bidirectionality by removing the strategy property in StrategyDetail and then it does properly save the details, but then I obviously don't have access to the strategy field inside the detail...
I've been looking for a solution on this for the past hour, looking at tutorials and what not, but I have not found a working solution.

Resources