Mongo query in Spring Boot - spring

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

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"
}
]
}
]

Spring boot mongodb #DBRef lazy loading not working for hashsets

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.

Apply metric tag from request header globally in SpringBoot with Micrometer

We have an app currently using spring cloud gateway, and we need to add a request header to every metric globally, which was easy enough for the out of the box metrics from the framework. The problem comes when we want to add our own custom metrics. None of the globally added tags are sent. This is my config:
#Configuration
public class MetricsConfig {
#Bean
public WebFluxTagsContributor webFluxTagsContributor() {
return (exchange, ex) -> buildRequestTags(exchange);
}
#Bean
public GatewayTagsProvider requestTagsProvider() {
return exchange -> buildRequestTags(exchange);
}
#NotNull
private Tags buildRequestTags(ServerWebExchange serverWebExchange) {
String clientApplication = HeadersUtils.get(serverWebExchange.getRequest().getHeaders(), "X-$%$#%$#");
return Tags.of(Tag.of(MetricTag.APP_FROM.lowered(), clientApplication));
}
}
And this is where my custom metric is added:
#Component
public class ProviderRoutesHeaderFilter implements WebFilter, Ordered {
private static final Logger LOG = LoggerFactory.getLogger(ProviderRoutesHeaderFilter.class);
public static final String A_HEADER_KEY = "x-*&%^";
private final MeterRegistry meterRegistry;
public ProviderRoutesHeaderFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
#Override
public int getOrder() {
return -2;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
var request = exchange.getRequest();
if(request.getPath().toString().contains("/someroute/") && !request.getHeaders().containsKey(A_HEADER_KEY)) {
traceUnspecifiedHeader(request);
return chain.filter(exchange.mutate()
.request(request.mutate()
.header(A_HEADER_KEY, NOT_SPECIFIED.name())
.build())
.build());
}
return chain.filter(exchange);
}
private void traceUnspecifiedHeader(ServerHttpRequest request) {
LOG.info(A_HEADER_KEY + " header not specified... applying default header");
Set<Tag> tags = Collections.singleton(Tag.of(MetricTag.PATH.lowered(), request.getPath().value()));
meterRegistry.counter("sp.gateway.a_not_specified", tags).increment();
}
}
With this setup I can see the tag in the request metrics:
localhost:8080/actuator/metrics/spring.cloud.gateway.requests
{
"name": "spring.cloud.gateway.requests",
"description": null,
"baseUnit": "milliseconds",
"measurements": [
{
"statistic": "COUNT",
"value": 1.0
},
{
"statistic": "TOTAL_TIME",
"value": 698.241891
},
{
"statistic": "MAX",
"value": 0.0
}
],
"availableTags": [
{
"tag": "routeUri",
"values": [
"http://www.***.com:80"
]
},
{
"tag": "routeId",
"values": [
"******"
]
},
{
"tag": "httpMethod",
"values": [
"GET"
]
},
{
"tag": "app_from",
"values": [ -----> this is my tag
"andy"
]
},
{
"tag": "outcome",
"values": [
"CLIENT_ERROR"
]
},
{
"tag": "status",
"values": [
"BAD_REQUEST"
]
},
{
"tag": "httpStatusCode",
"values": [
"400"
]
}
]
}
But not in my custom metric:
localhost:8080/actuator/metrics/sp.gateway.a_not_specified
{
"name": "sp.gateway.a_not_specified",
"description": null,
"baseUnit": null,
"measurements": [
{
"statistic": "COUNT",
"value": 2.0
}
],
"availableTags": [
{
"tag": "path",
"values": [
"/pr****/***/**"
]
}
]
}
Is there some config I am missing to accomplish this?
Thank you!!
I think you can add the same tags used by the framework by hooking in the filter after the request has been handled and use the same tag provider as used by spring.cloud.gateway.requests and expose your custom tag as a static method.
Maybe you can use the doFinally method on the Mono, something like this (I have not tested if it works)
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doFinally(signalType -> {
Set<Tag> tags = Collections.singleton(Tag.of(MetricTag.PATH.lowered(), exchange.getRequest().getPath().value()));
tags.addAll(new GatewayHttpTagsProvider().apply(exchange).stream().toList());
tags.add(buildRequestTags(exchange));
meterRegistry.counter("sp.gateway.a_not_specified", tags).increment();
});
}

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.

Spring Boot - Flatten JSON Entity returned by JPA specification for exporting to excel

I am creating a Spring Boot project for inventory management. I have an Entity called InwardInventory that has one to many relationship with another entity called InwardOutwardList. I am using JPA Specification to filter the Entity InwardInventory and it is working fine. Response I receive after filter is
{
"inwardInventory": {
"content": [
{
"inwardid": 19497,
"date": "2019-05-28",
"vehicleNo": "TRUCK",
"supplierSlipNo": "",
"ourSlipNo": "",
"inwardOutwardList": [
{
"entryid": 19499,
"product": {
"productName": "Cement",
},
"quantity": 100.0
},
{
"entryid": 19500,
"product": {
"productName": "Iron",
},
"quantity": 30.0
}
],
"warehouse": {
"warehouseName": "war2"
},
"supplier": {
"name": "Bright Traders"
}
}
]
}
}
Now, I want to export this data to excel. So, I need to flatten this response to something like this.
{
"inwardInventory": {
"content": [
{
"inwardid": 19497,
"date": "2019-05-28",
"vehicleNo": "TRUCK",
"supplierSlipNo": "",
"ourSlipNo": "",
"entryid": 19499,
"productName": "Cement",
"quantity": 100.0,
"warehouseName": "war2",
"name": "Bright Traders"
},
{
"inwardid": 19497,
"date": "2019-05-28",
"vehicleNo": "TRUCK",
"supplierSlipNo": "",
"ourSlipNo": "",
"entryid": 19500,
"productName": "Iron",
"quantity": 30.0,
"warehouseName": "war2",
"name": "Bright Traders"
}
]
}
}
I know that I can do this by iterating over each inward inventory and then nested iteration over each product and creating custom DAO. However, that doesn't seem to be optimised solution.
I also cannot use projections or native queries with custom select columns because they cannot be integrated with jpa specification. Can someone suggest me best approach that can be used to achieve this in most optimal way.
Thank You.
Handled this through custom serializer. Sample code below
#Component
public class OutwardExportSerializer extends StdSerializer<OutwardInventoryExportDAO>
{
private static final long serialVersionUID = 1L;
protected OutwardExportSerializer(Class<OutwardInventoryExportDAO> t)
{
super(t);
}
protected OutwardExportSerializer()
{
this(null);
}
#Override
public void serialize(OutwardInventoryExportDAO value, JsonGenerator gen, SerializerProvider provider) throws IOException
{
DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
for(InwardOutwardList ioList:value.getInwardOutwardList())
{
System.out.println("Serialization starts for OutWardID"+value.getOutwardid());
gen.writeStartObject();
gen.writeNumberField("outwardid", value.getOutwardid());
gen.writeStringField("date",df.format(value.getDate()));
gen.writeStringField("purpose",value.getPurpose()!=null?value.getPurpose():"");
gen.writeStringField("slipNo",value.getSlipNo()!=null?value.getSlipNo():"");
gen.writeStringField("product",ioList.getProduct().getProductName()!=null?ioList.getProduct().getProductName():"");
gen.writeStringField("measurement unit",ioList.getProduct().getMeasurementUnit()!=null?ioList.getProduct().getMeasurementUnit():"");
gen.writeNumberField("quantity",ioList.getQuantity()!=null?ioList.getQuantity():0.0);
gen.writeNumberField("closing stock",ioList.getClosingStock()!=null?ioList.getClosingStock():0.0);
gen.writeStringField("warehouse",value.getWarehouse()!=null?value.getWarehouse():"");
gen.writeStringField("contractor",value.getContractor()!=null?value.getContractor():"");
gen.writeStringField("additionalInfo",value.getAdditionalInfo()!=null?value.getAdditionalInfo():"");
gen.writeStringField("usageLocation",value.getUsageLocation()!=null?value.getUsageLocation():"");
gen.writeStringField("usageArea",value.getUsageArea()!=null?value.getUsageArea():"");
gen.writeEndObject();
System.out.println("Serialization ends for OutWardID"+value.getOutwardid());
}
}
}

Resources