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

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

Related

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.

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 Data Rest output join column object as JSON field when querying single resource

I have Apartment entity:
#Entity
public class Apartment extends AbstractEntity {
private String name;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(nullable = false)
#RestResource(exported = false)
private Address address;
private String website;
#OneToMany(mappedBy = "apartment")
#RestResource(exported = false)
private Set<FloorPlan> floorPlans;
...
FloorPlan entity:
#Entity
public class FloorPlan extends AbstractEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "apt_id", nullable = false)
private Apartment apartment;
private float bed;
private float bath;
private int priceFrom;
...
I applied excerptProjection to Floorplan to only show bed, bath and priceFrom. When I query apartments collection, the json output looks ok:
{
"_embedded": {
"apartments": [
{
"name": "Avalon Silicon Valley",
"website": "https://www.avaloncommunities.com/california/sunnyvale-apartments/avalon-silicon-valley",
"address": {
"streetNumber": "1257",
"street": "Lakeside Drive",
"city": "Sunnyvale",
"state": "CA",
"zipCode": "94085",
"fullAddress": "1257 Lakeside Drive, Sunnyvale, CA 94085"
},
"floorPlans": [
{
"bed": 3,
"bath": 3,
"priceFrom": 4495
},
{
"bed": 3,
"bath": 2,
"priceFrom": 4760
},
However if I do a single resource like http://localhost:8080/ag-api/apartments/1
floorplans will output Apartment Object as one of its field:
{
"name": "Avalon Silicon Valley",
"address": {
"streetNumber": "1257",
"street": "Lakeside Drive",
"city": "Sunnyvale",
"state": "CA",
"zipCode": "94085",
"fullAddress": "1257 Lakeside Drive, Sunnyvale, CA 94085"
},
"website": "https://www.avaloncommunities.com/california/sunnyvale-apartments/avalon-silicon-valley",
"floorPlans": [
{
"bed": 3,
"bath": 3,
"priceFrom": 4495,
"_embedded": {
"apartment": {
"name": "Avalon Silicon Valley",
"website": "https://www.avaloncommunities.com/california/sunnyvale-apartments/avalon-silicon-valley",
"address": {
"streetNumber": "1257",
"street": "Lakeside Drive",
"city": "Sunnyvale",
"state": "CA",
"zipCode": "94085",
"fullAddress": "1257 Lakeside Drive, Sunnyvale, CA 94085"
},
"floorPlans": [
Anyone got an idea what might be going on? really appreciate it. Thanks
Not sure if it will help, but in documentation I found in order to omit "_embedded" in you response you'll need to add:
"spring.hateoas.use-hal-as-default-json-media-type=false" to application.properties.

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

WebAPI 2 Odata Filter not working

I have a problem with using multiple filters on my WebAPI 2 Odata project.
We only want JSON output and only have one object type to query so we set the url to "/" without the possibility to use a different controller.
I have an object I want to query with the following properties:
public class Content
{
public int Id { get; set; }
public string Title { get; set; }
public string Excerpt { get; set; }
public string Link { get; set; }
public IList<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string Name { get; set; }
}
And the controller code looks like this:
public class ContentController : ApiController
{
private readonly IContentRepository _repository;
// constructor
public ContentController(IContentRepository repository)
{
_repository = repository;
}
[Queryable]
public IQueryable<Content> Index()
{
// IContentRepository.GetAll returns an IEnumerable List of Content
return _repository.GetAll().AsQueryable();
}
}
Now, I've mocked some testdata with adding multiple objects to the repository that have multiple tags with values set to either (test1, test2 or test3). Now when i Query
http://localhost:xxx?$filter=Tags/any(o: o/Id eq 'test1')
I get all objects with Tag/Id set to 'test1'. But if I query
http://localhost:xxx?$filter=Tags/any(o: o/Id eq 'test1' and o/Id eq 'test2')
I get no result (JSON return = []). But it should return objects that have both tags.
What am I doing wrong?
EDIT:
My sample data JSON looks like this:
[
{
"Id": 1,
"Title": "TESTOBJECT 1",
"Excerpt": "",
"Link": "",
"Tags": [
{
"Id": "test1",
"Name": "Test Tag 1",
}
],
},
{
"Id": 2,
"Title": "TESTOBJECT 2",
"Excerpt": "",
"Link": "",
"Tags": [
{
"Id": "test2",
"Name": "Test Tag 2",
}
],
},
{
"Id": 3,
"Title": "TESTOBJECT 3",
"Excerpt": "",
"Link": "",
"Tags": [
{
"Id": "test3",
"Name": "Test Tag 3",
}
],
},
{
"Id": 4,
"Title": "TESTOBJECT 4",
"Excerpt": "",
"Link": "",
"Tags": [
{
"Id": "test1",
"Name": "Test Tag 1",
},
{
"Id": "test2",
"Name": "Test Tag 2",
}
],
},
{
"Id": 5,
"Title": "TESTOBJECT 5",
"Excerpt": "",
"Link": "",
"Tags": [
{
"Id": "test1",
"Name": "Test Tag 1",
}
],
}
]
Now query one gives me object 1,4,5 and I would expect query two to give me object 4. How can I accomplish this with odata?
Did you want this?
http://localhost:xxx?$filter=Tags/any(o: o/Id eq 'test1') and Tags/any(o: o/Id eq 'test2')
((X == "test1") AND (X == "test2")) will always return false!
If you want to identify Tags whose property Id is test1 or test2, use the or operator.

Resources