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

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.

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;

spring-data-jpa - Limit how deep objects populate

I have the following entities:
#Entity
#Table(name = "business", schema = "public")
public class Business {
// some properties
}
#Entity
#Table(name = "appuser", schema = "public")
public class AppUser implements UserDetails {
// some properties
#JsonManagedReference
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
private List<UserBusinessRole> userBusinessRoles = new ArrayList<>();
}
#Entity
#Table(name = "appuser_business_role", schema = "public")
public class UserBusinessRole {
// some properties
#ManyToOne
#JoinColumn(name = "business_id")
private Business business;
}
These work without issue when calling individually, however, I also have an entity that has business AND app user:
#Entity
#Table(name = "import_session", schema = "public")
public class ImportSession {
// some properties
#JsonIgnore
#ManyToOne()
#JoinColumn(
name = "requester_user_id",
referencedColumnName = "id"
)
private AppUser requester;
#ManyToOne
#JoinColumn(name = "business_id")
private Business business;
}
But it returns duplicate values for business like below (listed under roles and in the root object):
{
"id": 14,
...
"requesterDto": {
"id": 123,
"emailAddress": "bar#bar.com",
"userBusinessRolesDto": [
{
"id": 6,
"type": "ADMIN",
"businessDto": {
"name": "Foo Inc"
...
}
}
]
},
"businessDto": {
"name": "Foo Inc"
}
}
Is there a way to make it ONLY return certain fields, or control how 'deep' it populates, without a lot of manual fiddeling / creating separate DTOs all over? So it would look something like this for example:
{
"id": 14,
...
"requesterDto": {
"id": 123,
"emailAddress": "bar#bar.com"
},
"businessDto": {
"name": "Foo Inc"
...
}
}

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

Using Entity Graph with DTO Projection using ResultTransformer returns null value

Hello I am new in jpa + criteria API + hibernate..
I have doubt related to use of ResultTransformer in jpa criteria API.
I am having two entity Department and Employee. one to many mapping between department and employee. i want to use entitygraph with DTO projection
1. Department
#Entity
#NamedEntityGraph(name = "departmentWithEmployee", attributeNodes = #NamedAttributeNode("setOfEmployee"))
#Table(name = "tblDepartment")
public class Department {
#Id
private String id;
private String name;
#OneToMany(mappedBy = "department")
private Set<Employee> setOfEmployee;
//....getter & setter
}
2. Employee
#Entity
#Table(name = "tblEmployee")
public class Employee {
#Id
private String id;
#ManyToOne
#JsonIgnore
private Department department;
private String firstName;
private String lastName;
//...getter & setter
}
DepartmentDTO.java
public class DepartmentDTO implements Serializable {
private String id;
private String name;
private Set<EmployeeDTO> setOfEmployee;
//... getter & setter..
}
I am executing query with entity graph and I want to get all departments from database and serialize with DepartmentDTO.java
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<DepartmentDTO> criteria = builder.createQuery(DepartmentDTO.class);
Root root = criteria.from(Department.class);
criteria.select(root);
EntityGraph graph = entityManager.getEntityGraph("departmentWithEmployee");
List<DepartmentDTO> list = entityManager.createQuery(criteria).setHint("javax.persistence.fetchgraph", graph)
.unwrap(org.hibernate.Query.class).setResultTransformer(Transformers.aliasToBean(DepartmentDTO.class)).list();
when i will get size of list it will give me correct result but it will give list of department with null value like
(In database i am having total 3 departments)
Output :
[
{
"id": null,
"name": null,
"setOfEmployee": null
},
{
"id": null,
"name": null,
"setOfEmployee": null
},
{
"id": null,
"name": null,
"setOfEmployee": null
}
]
I am getting all fields with null value.
So what is the issue here , is any mistake in use of ResultTransformer? Or is any better way to execute this query where I can get records using DTO ..?

Resources