Bidirectional mapping does not pick up parent - spring

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.

Related

FetchType.LAZY is working as FetchType.EAGER

I have a problem with hibernate, namely with lazy data fetching. I have two entities and one of them clinic is having relation #OneToMany. I setup FetchType to LAZY, but still, when I am making request to get all clinic, I still have an array in the clinic entity. The questions is - why I am still getting full #OneToMany array? Shouldn't it be null?
Here I provided my entity classes
#Getter
#Setter
#Entity(name = "clinic")
public class Clinic {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy="clinic")
private List<Doctor> doctors;
}
And doctor entity
#Getter
#Setter
#Entity(name = "doctor")
public class Doctor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private Clinic clinic;
}
And my controller, which is JpaRepository interface
#RestController
#RequestMapping("clinics")
public class ClinicController {
private final ClinicRepository clinicRepository;
public ClinicController(ClinicRepository clinicRepository) {this.clinicRepository = clinicRepository;}
#GetMapping()
public List<Clinic> getAllClinics() {
return clinicRepository.findAll();
}
}
I also logged SQL provided by Hibernate and it looks like this
Hibernate: select clinic0_.id as id1_0_, clinic0_.name as name2_0_ from clinic clinic0_
Hibernate: select doctors0_.clinic_id as clinic_i4_1_0_, doctors0_.id as id1_1_0_, doctors0_.id as id1_1_1_, doctors0_.clinic_id as clinic_i4_1_1_, doctors0_.firstname as firstnam2_1_1_, doctors0_.lastname as lastname3_1_1_ from doctor doctors0_ where doctors0_.clinic_id=?
Hibernate: select doctors0_.clinic_id as clinic_i4_1_0_, doctors0_.id as id1_1_0_, doctors0_.id as id1_1_1_, doctors0_.clinic_id as clinic_i4_1_1_, doctors0_.firstname as firstnam2_1_1_, doctors0_.lastname as lastname3_1_1_ from doctor doctors0_ where doctors0_.clinic_id=?
As I understand, it supposed to send only one query (first select).
And of course, I also will provide an answer that I am getting right now
[
{
"id": 1,
"name": "Clinic 1",
"doctors": [
{
"id": 1,
"firstname": "Bob",
"lastname": "Fisher"
},
{
"id": 2,
"firstname": "John",
"lastname": "Cena"
}
]
},
{
"id": 2,
"name": "Clinic 2",
"doctors": [
{
"id": 3,
"firstname": "Some",
"lastname": "guy"
}
]
}
]
The association is lazily loaded because Jackson (JSON serialization library) simply serializes this proxy collection. Lazy loading is possible because Spring enables the open-session-in-view by default. If you turn that config off, you will see a LazyInitializationException.
What you want though, is to tell Jackson not to serialize the collection, by annotating it with #JsonIgnore:
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy="clinic")
private List<Doctor> doctors;

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;
}

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

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

Cascade persist with Spring Data REST

I have two Entities with bidirectional relation OtM <-> MtO. I also use cascade PERSIST because I would like to persist the data at once.
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String title;
#ManyToOne(cascade = CascadeType.PERSIST)
private Author author;
}
#Entity
public class Author {
#Id
#GeneratedValue
private Long id;
private String title; //mr, mrs
private String name;
#OneToMany(mappedBy = "author")
private List<Book> books;
}
I have created BookRepository at first and exposed it with Spring Data REST.
#RepositoryRestResource
public interface BookRepository extends JpaRepository<Book, Long>{
}
When I sent POST request with JSON:
{
"title": "Some title",
"author": {
"title": "Mr",
"name": "John Doe"
}
}
everything works and both book and author entities are persisted. Now I wanted to expose data about authors so I've added another Repository:
#RepositoryRestResource
public interface AuthorRepository extends JpaRepository<Author, Long> {
}
Now when I send the same JSON, the book entity is persisted, but author entity is not. What is more, the book title is now "Mr".
I do not understand this weird behaviour. Why with single repository everything works fine, but after adding another one, Spring is not only not persisting related author entity, but it is taking the wrong "title" field from JSON that I've sent?
Is there any way to persist the data with single request or I always have to persist author first and then persist the book with HAL format like "author": "http://.../createdAuthorId"?
See the office guide. Here an example:
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String title;
#ManyToOne(cascade = CascadeType.PERSIST)
#RestResource(exported = false)
private Author author;
}
I add #RestResource(exported = false) to the author property.
The Author entity should not be modified.
You can cascade-persist author like this:
{
"title": "Some title",
"author": {
"title": "Mr",
"name": "John Doe"
}
}

Resources