FetchType.LAZY is working as FetchType.EAGER - spring

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;

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.

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.

JPA Repository many to one

I have Student entity and Course entity. This is #ManyToOne relationship i.e. Student may attend only one course at a time, but courses may have multiple students.
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String studentId;
private String firstName;
private String secondName;
#ManyToOne
#JoinColumn(name = "course_id")
//#JsonIgnore
private Course course;
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "course", orphanRemoval = true, targetEntity = Student.class)
private List<Student> students = new ArrayList<>();
I post my data with the following json:
{
"id": 1,
"courseName": "course134",
"students" : [
{
"id" : 1,
"studentId": "123",
"firstName": "John1",
"secondName": "Name1"
},
{
"id" : 2,
"studentId": "1234567",
"firstName": "John2",
"secondName": "Name2"
}
then, as I get courses I receive:
{
"id": 1,
"courseName": "course134",
"students": []
}
How to list Students attending specific course?
I made up a Query in StudentRepository
#Query("SELECT s from Student s where s.id = :courseName")
Optional<Student> getStudentByCourseName(String courseName);
Still not working.
this is my Repository code:
#Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
Optional<Course> findCourseByCourseName(String courseName);
#Query("SELECT c.students FROM Course c WHERE c.courseName = :courseName")
Optional<Student> getStudentsByCourseName(String courseName);
}
this is my Service method
public Optional<Student> findStudentByCourse(String courseName){
return courseRepository.getStudentsByCourseName(courseName);
}
and finally my Controller:
#GetMapping("/student/course/{courseName}")
public ResponseEntity<Student> findCoursesWithStudentId(#PathVariable String courseName) {
Optional<Student> byCourseName = studentService.findStudentByCourse(courseName);
if (byCourseName.isPresent()) {
return ResponseEntity.ok(byCourseName.get());
} else {
return ResponseEntity.notFound().build();
}
}
You should query the Course table, not the Student table. Also, the query will return the list, not just one entity, so change your method's return type also...
#Query("SELECT c.students FROM Course c WHERE c.courseName = :courseName")
List<Student> getStudentsByCourseName(String courseName) {}
edit
You can always do it like so:
Excute the simple method:
Course findByCourseName(String courseName) {}
and then just get its Students by a simple:
course.getStudents();

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