Spring boot mongodb #DBRef lazy loading not working for hashsets - spring-boot

Issue: Don't want to fetch the full referenced child objects just the Id.
Partner.java
#Data
#Document
#Builder
public class Partner {
#Id
private String id;
#DBRef(lazy = true) // working as expected
private User user;
#DBRef(lazy = true) // not working, still loads full objects
#Builder.Default
private HashSet<Product> products = new HashSet<>();
}
Product.java
#Data
#Document
#Builder
public class Product {
#Id
private String id;
private Name name;
}
I know that #DBRef is not recommended for such cases. But the project is already built that way and is expensive to change at the moment.
So far tried:
excluding the child array field from the query but then I do not get the Ids as well.
excluding the products.$ref from the query.
#Query(value="{ '_id' : ?0 }", fields="{ products.$ref: 0 }")
Optional<Partner> findByIdWithoutProducts(String id);
Expected:
{
"_id": {
"$oid": "634692559f31e246984436c8"
},
"user": {
"$ref": "user",
"$id": "f689bae8-7442-4355-ae9c-65d22a96ear4"
},
"products": [
{
"$id": {
"$oid": "633d92d2310687496fb852e6"
}
},
{
"$id": {
"$oid": "633d92d2310687496fb852d5"
}
}
}
}
Actual:
{
"_id": {
"$oid": "634692559f31e246984436c8"
},
"user": {
"$ref": "user",
"$id": "f689bae8-7442-4355-ae9c-65d22a96ear4"
},
"products": [
{
"$id": {
"$oid": "633d92d2310687496fb852e6"
},
"name": "abcd" // not wanted
},
{
"$id": {
"$oid": "633d92d2310687496fb852d5"
},
"name": "xyz" // not wanted
}
}
}
My goal is to fetch the parent and just list of Ids of the child objects. Any other way is also appreciated.

Related

How to map query result to List<Object> in R2DBC?

I am trying to migrate my JDBC based application to R2DBC, however I struggle with List based objects. How can I map the result from query to "List" field?
I was using ResultSetExtractor, but in R2DBC there is no option like that so I try to do it manually. I use DatabaseClient with manual mapping, however this mapping does not work with List type fields.
public class Employee {
private Long id;
private String name;
private String surname;
private List\<Account\> accounts;
}
public class Account {
private String employeeId;
private String accountNumber;
}
public class EmployeeRepository {
private final DatabaseClient databaseClient;
public static final BiFunction<Row, RowMetadata, Employee> EMPLOYEE_MAPPER =
(row, rowMetadata) -> Employee.builder()
.id(row.get("id", String.class))
.name(row.get("name", String.class))
.surname(row.get("surname", String.class))
.accounts(List.of(new Account((row.get("employeeId"), (row.get("accountNumber")))))
.build();
public Flux<Employee> findCardByPan(Long id) {
return databaseClient.sql("SELECT id, name, surname, a.accountNumber FROM employee e JOIN account a on e.id = a.employeeId WHERE e.id = :id")
.bind("id", id)
.map(EMPLOYEE_MAPPER)
.all();
}
}
Current response model:
[
{
"id": "1",
"name": "Test User",
"surname": "Test User",
"accounts": [
{
"employeeId": "1",
"accountNumber": "98034"
}
]
},
{
"id": "1",
"name": "Test User",
"surname": "Test User",
"accounts": [
{
"employeeId": "1",
"accountNumber": "12456"
}
]
}
]
Expected response model:
[
{
"id": "1",
"name": "Test User",
"surname": "Test User",
"accounts": [
{
"employeeId": "1",
"accountNumber": "98034"
},
{
"employeeId": "1",
"accountNumber": "12456"
}
]
}
]

How to get consolidated Data in many to many with extra entity approach

Below are the Entities I have created to achieve Many to Many with an extra entity concept.
Company :
#Getter
#Setter
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "companyId")
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long companyId;
private String companyName;
#OneToMany(mappedBy = "company", fetch = FetchType.EAGER)
Set<CompanyUserMapping> companyUsers;
}
User :
#Getter
#Setter
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String email;
private String originalCompanyName;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
Set<CompanyUserMapping> companyUserMapping;
}
CompanyUserMapping
#Getter
#Setter
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class CompanyUserMapping {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "company_id")
private Company company;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
private boolean isExternal;
private boolean isActive;
}
Below is the way I am adding company with the user.
{
"company": {
"companyName": "XYZ company"
},
"user": {
"email": "sehwag#gmail.com",
"originalCompanyName": "XYZ company"
},
"active": true,
"external": false
}
And also trying to add the user to the existing company using below snippet.
Optional<User> optionalUser=userRepository.findByEmail(user.getEmail());
Optional<Company> optionalCompany = companyRepository.findById(companyId);
if(optionalUser.isPresent()) {
User existingUser=optionalUser.get();
Optional<CompanyUserMapping> optionalCompanyUserMapping=companyUserMappingRepository.findByCompanyAndUser(optionalCompany.get(),existingUser);
if(optionalCompanyUserMapping.isPresent()) {
throw new UserExistsForCompanyException("User exists for this company "+companyId);
}
CompanyUserMapping companyUserMapping =new CompanyUserMapping();
companyUserMapping.setCompany(optionalCompany.get());
companyUserMapping.setUser(existingUser);
companyUserMapping.setActive(true);
companyUserMapping.setExternal(false);
CompanyUserMapping savedcompanyUserMapping = companyUserMappingRepository.save(companyUserMapping);
return new ResponseEntity<CompanyUserMapping>(savedcompanyUserMapping, HttpStatus.CREATED);
}
CompanyUserMapping companyUserMapping =new CompanyUserMapping();
companyUserMapping.setCompany(optionalCompany.get());
companyUserMapping.setUser(user);
companyUserMapping.setActive(true);
companyUserMapping.setExternal(false);
CompanyUserMapping savedcompanyUserMapping = companyUserMappingRepository.save(companyUserMapping);
Here If I try to get Company details by Id, there is repeated information and also unwanted company information which is associated to few common users.The output Json is as below
{
"companyId": 1,
"companyName": "ABC company",
"companyUsers": [
{
"companyUserMappingId": 1,
"company": 1,
"user": {
"userId": 1,
"email": "sachintendulkar#gmail.com",
"companyUserMapping": [
1
]
},
"active": true,
"external": false
},
{
"companyUserMappingId": 3,
"company": 1,
"user": {
"userId": 3,
"email": "shanewarne#gmail.com",
"companyUserMapping": [
3,
{
"companyUserMappingId": 4,
"company": {
"companyId": 2,
"companyName": "XYZ company",
"companyUsers": [
{
"companyUserMappingId": 2,
"company": 2,
"user": {
"userId": 2,
"email": "sehwag#gmail.com",
"companyUserMapping": [
2
]
},
"active": true,
"external": false
},
4
]
},
"user": 3,
"active": true,
"external": false
}
]
},
"active": true,
"external": false
}
]
}
How do I get the consolidated data like below.
{
"companyId": 1,
"companyName": "ABC company",
"companyUsers": [
{
"companyUserMappingId": 1,
"user": {
"userId": 1,
"email": "sachintendulkar#gmail.com"
},
"active": true,
"external": false
},
{
"companyUserMappingId": 3,
"user": {
"userId": 3,
"email": "shanewarne#gmail.com"
},
"active": true,
"external": false
}
]
}
And then if I call get User by UserId, the consolidated data should be like below.
{
"userId": 1,
"email": "sachintendulkar#gmail.com",
"associatedCompany": [
{
"companyId": 1,
"companyName": "ABC company"
}
]
}

Show only object id in OneToMany List

I have these two classes:
Quota Class
#Entity
#Data
#EqualsAndHashCode(callSuper=false)
#Table(name="quotas")
#Relation(collectionRelation = "quotas", itemRelation = "quota")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Quota extends RepresentationModel<Quota> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "quota")
#JsonIdentityReference(alwaysAsId = true)
private List<Customer> customers;
private String type;
private boolean flatrate;
#CreatedDate
private long createdDate;
#LastModifiedDate
private long lastModifiedDate;
And Customer:
#Entity
#Data
#EqualsAndHashCode(callSuper=false)
#Table(name = "customers")
#Relation(collectionRelation = "customers", itemRelation = "customer")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Customer extends RepresentationModel<Customer> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String customerNumber;
#CreatedDate
private long createdDate;
#LastModifiedDate
private long lastModifiedDate;
#ManyToOne
#JoinColumn(name = "quota_id", referencedColumnName = "id")
#JsonProperty("quotaId")
#JsonIdentityReference(alwaysAsId = true)
private Quota quota;
}
What i get when i make a GET request on all Customers:
{
"_embedded": {
"customers": [
{
"id": 3,
"name": "Customer Test3",
"customerNumber": "45678",
"createdDate": 1596117132045,
"lastModifiedDate": 1596117132045,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/3"
}
},
"quotaId": 1
},
{
"id": 1,
"name": "test3",
"customerNumber": "12345",
"createdDate": 1596111304535,
"lastModifiedDate": 1596186450456,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/1"
}
},
"quotaId": 1
},
{
"id": 2,
"name": "Customer Test2",
"customerNumber": "23456",
"createdDate": 1596112131934,
"lastModifiedDate": 1596112131934,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/2"
}
},
"quotaId": 2
},
{
"id": 4,
"name": "Customer Test4",
"customerNumber": "34567",
"createdDate": 1596117145795,
"lastModifiedDate": 1596117145795,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/4"
}
},
"quotaId": 2
},
{
"id": 6,
"name": "Customer Test6",
"customerNumber": "12345",
"createdDate": 1596187250598,
"lastModifiedDate": 1596187250598,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/6"
}
},
"quotaId": null
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/seminars?page=0&size=20&sort=quota,asc"
}
},
"page": {
"size": 20,
"totalElements": 5,
"totalPages": 1,
"number": 0
}
}
What i get when i make a GET request in all Quotas:
{
"_embedded": {
"quotas": [
{
"id": 1,
"customers": [
{
"id": 3,
"name": "Customer Test3",
"customerNumber": "45678",
"createdDate": 1596117132045,
"lastModifiedDate": 1596117132045,
"quotaId": 1
},
{
"id": 1,
"name": "test3",
"customerNumber": "12345",
"createdDate": 1596111304535,
"lastModifiedDate": 1596186450456,
"quotaId": 1
}
],
"type": "paid",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/1"
}
}
},
{
"id": 2,
"customers": [
{
"id": 2,
"name": "Customer Test2",
"customerNumber": "23456",
"createdDate": 1596112131934,
"lastModifiedDate": 1596112131934,
"quotaId": 2
},
{
"id": 4,
"name": "Customer Test4",
"customerNumber": "34567",
"createdDate": 1596117145795,
"lastModifiedDate": 1596117145795,
"quotaId": 2
}
],
"type": "demo",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/quotas?page=0&size=10&sort=id,asc"
}
},
"page": {
"size": 10,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
I am happy with the result from Customers, but the result from quotas i would want to look like this:
{
"_embedded": {
"quotas": [
{
"id": 1,
"customers": [3,1],
"type": "paid",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/1"
}
}
},
{
"id": 2,
"customers": [2,4],
"type": "demo",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/quotas?page=0&size=10&sort=id,asc"
}
},
"page": {
"size": 10,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
For json serialization i use 'com.fasterxml.jackson.annotation.*'
I already tried it with using the #JsonBackRefference, #JsonIgnore, #JsonIngoreProperties and #JsonIdentityReference annotations but never got the wanted result.
I would recommend you introduce DTOs to model this rather than trying to annotate the entity classes with JSON annotations. At some point you will probably have conflicting needs i.e. one use case needs a field and another doesn't and then you will have to think about abstracting this anyway.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Quota.class)
public abstract class QuotaDto extends RepresentationModel<QuotaDto> {
public abstract Integer getId();
#Mapping("customers.id")
public abstract List<Integer> getCustomers();
public abstract String getType();
public abstract boolean isFlatrate();
public abstract long getCreatedDate();
public abstract long getLastModifiedDate();
}
#EntityView(Customer.class)
public abstract class CustomerDto extends RepresentationModel<CustomerDto> {
public abstract Integer getId();
public abstract String getName();
public abstract String getCustomerNumber();
public abstract long getCreatedDate();
public abstract long getLastModifiedDate();
#Mapping("quota.id")
public abstract Integer getQuotaId();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CustomerDto dto = entityViewManager.find(entityManager, CustomerDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You are in control of the structure and can map anything to the properties. It will only fetch the mappings that you tell it to fetch.

Mongo query in Spring Boot

Please help me to get the below complex mongo query using mongoTemplate in spring
i would like to retrieve the collection only if "NAME": "UserID1"?
{
"_id": "12345",
"A": [{
"B1": {
"NAME": "test1",
"C1": [{
"D1": [{
"NAME": "UserID1"
},
{
"NAME": "UserID2"
}
]
}]
}
},
{
"B2": {
"NAME": "test2",
"C2": [{
"D2": [{
"NAME": "UserID3"
},
{
"NAME": "UserID4"
}
]
}]
}
}]
}
Thanks.
for example if assume your class like this:
#Document
public class FirstClass{
#Id
String id;
List<SecondClass> A;
public class SecondClass{
string Name;
List<ThirdClass> C1;
}
}
in mongoTemplate:
...
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
...
#Component
public ServiceClass{
private final MongoTemplate mongoTemplate;
ServiceClass(MongoTemplate mongoTemplate){
this.mongoTemplate = mongoTemplate;
}
public List<FirstClass> getResult(String searchInput){
final Criteria criteria =
Criteria.where("A.name").is(searchInput);
List<FirstClass> result = mongoTemplate.find(new Query(criteria),FirstClass.class);
}
}
and you can use spring data mongo query like this:
public interface FirstClassRepository extends MongoRepository<FirstClass, String> {
List<FirstClass> findByA_Name(String name);
}

How can i customize the dafault behavior of jackson when returning a ResponseEntity<> with PagedResources<>?

Im using spring boot on my proyect. Basically, all i want is to include inline entities in a response of a GET method while still using the advantages of spring-hateoas (_embedded and links).
Having this class:
#Entity
#Table(name = "sub_specialty", schema = "public", catalog = "icorelab")
public class SubSpecialty {
private Integer id;
private String name;
private Boolean active;
private Date createdAt;
private Date deletedAt;
private Specialty specialty;
private String description;
#ManyToOne
#JoinColumn(name = "specialty_id", referencedColumnName = "id", nullable = false)
public Specialty getSpecialtyBySpecialtyId() {
return specialty;
}
I set up repository with PagingAndSorting capabilities, also it extends CustomRepository to provide custom methods. This repository uses a projection so an iternal entity gets serialized as mentioned here
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts.excerpts
#RepositoryRestResource(collectionResourceRel = "sub_specialties", path = "sub_specialties",
excerptProjection = InlineSpecialty.class)
public interface SubSpecialtyRepository extends PagingAndSortingRepository<SubSpecialty, Integer>,SubSpecialtyRepositoryCustom {
The in a controller, i return the response by using Page and PageResourcesAssembler
Page<SubSpecialty> page = subSpecialtyRepository.filter(name,description,active,specialty,limit,offset,pageable.getSort());
PagedResources<SubSpecialty> pagedResources = pagedResourcesAssembler.toResource(page,subSpecialtyResourceAssembler);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/sub_specialties/filter", offset, limit);
ResponseEntity<?> response = new ResponseEntity<>(pagedResources, headers, HttpStatus.OK);
return response;
The problem is that the message does not include Specialty info, just the link
{
"_embedded": {
"sub_specialties": [
{
"id": 107,
"name": "PHARMACOLOGY",
"active": false,
"createdAt": "2017-09-30",
"deletedAt": null,
"description": null,
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/107{?projection}",
"templated": true
},
"subSpecialty": {
"href": "http://localhost:8080/sub_specialties/107{?projection}",
"templated": true
},
"specialty": {
"href": "http://localhost:8080/specialties/16"
}
}
},
{
"id": 104,
"name": "PHARMACOLOGY",
"active": false,
"createdAt": "2017-09-30",
"deletedAt": null,
"description": null,
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/104{?projection}",
"templated": true
},
"subSpecialty": {
"href": "http://localhost:8080/sub_specialties/104{?projection}",
"templated": true
},
"specialty": {
"href": "http://localhost:8080/specialties/16"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/filter?page=0&size=20"
}
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
Is there anyway to achieve this? I thought i could use an annotation on the entity domain class or a custom jackson serializer.The Specialty entity is included in the response, however is not included in the message that receives the client. I suppose there is some default configuration for jackson that detects a repository of type Specialty, and the just includes the link in the response.

Resources