How to query jsonb datatype (Postgresql) in java code using Spring webflux & R2dbc - spring-boot

I have postgresql db with below structure
CREATE TABLE products (
id bigserial,
name varchar(30) NOT NULL,
owner_id bigint,
pdata jsonb NOT NULL,
PRIMARY KEY (id)
);
Here pdata is of jsonb datType
exmaple of pdata(jsonb dataType)
"facility": {
"parent": {
"type": "City"
},
"facilityId": "Z00TRIZR6KAQ6"
}
}
I
I run the below query from PGadmin it works fine. I get the desirable result
from techwriting.products
where pdata ->'facility' ->'parent'->>'type'='City'
and pdata ->'facility' ->>'facilityId'='Z00TRIZR6KAQ6';
Basically above query is checking various attributes in jsonb value and giving the result.
I am running the same query in java code using spring webflux and R2dbc. I am able to get the same response in java.
Model class
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table("products")
public class Product {
#Id
private Long id;
private String name;
private String pdata;
#Column("owner_id")
private long ownerId;
}
Repository class
import com.maersk.jsonb.model.Product;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
public interface ProductRepository extends ReactiveCrudRepository<Product, Long> {
#Query(value="select * from techwriting.products where pdata ->'facility' ->'parent'->>'type'='City' and pdata ->'facility' ->>'facilityId'='Z00TRIZR6KAQ6'")
Flux<Product> findAllByName();
}
Response: We are getting below response. Response is correct with same no of records .
[
{
"id": 1,
"name": "xyz",
"pdata": "{\"facility\": {\"parent\": {\"type\": \"BBSR\"}, \"facilityId\": \"Z00TRIZR6KAQ6\"}}",
"ownerId": 2
}
]
My Question are:
In repository class #Query contains all hardcoded value. How to do them dynamically?
Response contains pdata as String. However json data stored in postgresql and what I am getting the structure is different.
pdata(jsonb type) in postgresql:
{
"facility": {
"parent": {
"type": "BBSR"
},
"facilityId": "Z00TRIZR6KAQ6"
}
}
getting in response:
"{\"facility\": {\"parent\": {\"type\": \"BBSR\"}, \"facilityId\": \"Z00TRIZR6KAQ6\"}}"
How can we convert String to as stored in db as shown above?

My answer to Q1.
#Query(value="select * from techwriting.products where pdata ->'facility' ->'parent'->>'type'=$1 and pdata ->'facility' ->>'facilityId'=$2")
Flux<Product> findAllByfacilityIdandtype(String type, String facilityId);

Related

Failed parsing for a LocalDate value while using Spring Data Rest

I am still a newbie with Spring Data Rest and I'm having some issues with it while I have to parse a LocalDate value to an endpoint. I have searched info's in other topics too but I'm still stucked, this is the problem:
I have one Entity with this code .
#Entity
#Table(name="calendario_parcheggio")
#Setter
#Getter
public class CalendarioParcheggio {
#Id
#Column(name="data_parcheggio")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE )
#JsonFormat(pattern="yyyy-MM-dd")
private LocalDate data;
#Column(columnDefinition = "ENUM('ATTIVO', 'ARCHIVIATO')")
#Enumerated(EnumType.STRING)
private Stato stato;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="data_parcheggio")
private List<Parcheggio> parcheggio;
public enum Stato {
ATTIVO,
ARCHIVIATO,
}
}
It's an Entity linking the Date and its status for a Parking that works hourly.Matching this table on MySQL
CREATE TABLE calendario_parcheggio (
data_parcheggio DATE PRIMARY KEY,
stato ENUM ('ATTIVO','ARCHIVIATO') NOT NULL DEFAULT ('ATTIVO')
);
When I start the server everything is ok , but when i try (by browser or Postman) to check the data of a particular instance (in my case : "http://localhost:8080/parkingsystem/api/calendario-parcheggio/2022-10-18") ,I get this problem :
{"cause":
{"cause":
{"cause": null,
"message": "Text '2022-10-18' could not be parsed at index 2"
},
"message": "Parse attempt failed for value [2022-10-18]"
},
"message": "Failed to convert from type [java.lang.String] to type [java.time.LocalDate] for value '2022-10-18';
nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2022-10-18]"
}
And this is the Repository
#RepositoryRestResource(collectionResourceRel="calendarioParcheggio", path="calendario-parcheggio")
public interface CalendarioParcheggioRepository extends JpaRepository<CalendarioParcheggio, LocalDate> {
}
Can you help me to find the solution please?I hope I have explained the problem well enough, my English is still in training :)

How to do not send #IdClass object in Spring JSON queries

I'm setting a server to get a CRUD api from a postgresql Database using JPA. Everytime I want to expose an object from the DB it duplicate the idObject.
When I get an object from the database using springframework and send it after that, it duplicate the idObject like this:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1",
"siteIdObject": {
"siteId": 3,
"contractId": "1"
}
}
SiteId and contractId are repeating...
but I want something like that:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1"
}
I want to avoid using DTO because I think there is a better way but I don't find it. Since I'm using springFramework for just one or two month I'm maybe forgeting something...
there is the code:
Site code:
#Entity
#IdClass(SiteId.class)
#Table(name = "site", schema="public")
public class Site {
#Id
#Column(name="siteid")
private Integer siteId;
#Id
#Column(name="clientid")
private Integer contractId;
private String name;
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy = "site")
public Set<Device> devices;
//setter, getter, hash, equals, tostring, constructor empty one and full one
SiteId code:
public class SiteId implements Serializable {
private Integer siteId;
private Integer contractId;
// setter, getter, constructor empty and full, hash and equals
Thanks to help :)
Bessaix Daniel
If you are using Spring you might also be using Jackson so if you annotate your SiteIdclass with #JsonIgnoreType it shouldn't be serialized at all when the Site object is serialized.
I am however unsure if this will break your application logic now that the id object is not serialized anymore.

How to handle Spring Boot/ Spring Data projections with entity relationships (nested projection)

I'm trying to get nested projections working in Spring Boot. I have 2 entities, Parent and Child, wheras Parent has a unidirectional #OneToMany relationship to Child.
Here are the classes: (using Lombok-Annotations)
#Entity
#Data #NoArgsConstructor
public class Parent {
#Id
#GeneratedValue
private long id;
private String basic;
private String detail;
#OneToMany(fetch = FetchType.EAGER)
private List<Child> children;
public Parent(String basic, String detail, List<Child> children) {
this.basic = basic;
this.detail = detail;
this.children = children;
}
}
#Entity
#Data #NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private long id;
private String basic;
private String detail;
public Child(String basic, String detail) {
this.basic = basic;
this.detail = detail;
}
}
When im fetching the data without projecting i get the following:
[
{
"id": 1,
"basic": "parent-basic-1",
"detail": "parent-detail-1",
"children": [
{
"id": 1,
"basic": "child-basic-1",
"detail": "child-detail-1"
},
{
"id": 2,
"basic": "child-basic-2",
"detail": "child-detail-2"
}
]
},
{
"id": 2,
"basic": "parent-basic-2",
"detail": "parent-detail-2",
"children": [
{
"id": 3,
"basic": "child-basic-3",
"detail": "child-detail-3"
},
{
"id": 4,
"basic": "child-basic-4",
"detail": "child-detail-4"
}
]
}
and the goal would be the following:
{
"id": 1,
"basic": "parent-basic-1",
"children": [1,2]
},
{
"id": 2,
"basic": "parent-basic-2",
"children": [3,4]
}
However it seems completly impossible to achive this.
So far I've tried Constructor Projection:
#Value
public class ParentDto {
long id;
String basic;
// wanted to get it to work with just Child instead of ChildDto first, before getting ChildDto to work
Collection<Child> children;
public ParentDto(long id, String basic, Collection<Child> children) {
this.id = id;
this.basic = basic;
this.children = children;
}
}
// Constructor Projection in Repository
#Query("select new whz.springbootdemo.application.constructor_projection.ParentDto(p.id, p.basic, p.children) from Parent p")
List<ParentDto> findAllConstructorProjected();
but that leads to the following error:
could not prepare statement; SQL [select parent0_.id as col_0_0_, parent0_.basic as col_1_0_, . as col_2_0_ from parent parent0_ inner join parent_children children1_ on parent0_.id=children1_.parent_id inner join child child2_ on children1_.children_id=child2_.id]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
Trying Dynamic Projection:
// Dynamic Projection in Repository
List<ParentDto> findAllDynamicProjectionBy();
leads to the following error:
org.hibernate.hql.internal.ast.QuerySyntaxException:
Unable to locate appropriate constructor on class [whz.springbootdemo.application.constructor_projection.ParentDto].
Expected arguments are: <b>long, java.lang.String, whz.springbootdemo.application.child.Child</b>
[select new whz.springbootdemo.application.constructor_projection.ParentDto(generatedAlias0.id, generatedAlias0.basic, children) from whz.springbootdemo.application.parent.Parent as generatedAlias0 left join generatedAlias0.children as children]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [whz.springbootdemo.application.constructor_projection.ParentDto]. Expected arguments are: long, java.lang.String, whz.springbootdemo.application.child.Child [select new whz.springbootdemo.application.constructor_projection.ParentDto(generatedAlias0.id, generatedAlias0.basic, children) from whz.springbootdemo.application.parent.Parent as generatedAlias0 left join generatedAlias0.children as children]
which basically tells me that a join is executed, but the values arent grouped by the id of the parent, thus resulting in x rows, where x is the number of childs the parents has, each with the parents basic information and one of its childs information.
The only thing "working" is Interface Projection:
// Interface Projection in Repository
List<ParentDtoInterface> findAllInterfaceProjectedBy();
public interface ParentDtoInterface {
long getId();
String getBasic();
List<ChildDtoInterface> getChildren();
}
public interface ChildDtoInterface {
long getId();
}
It results in:
[
{
"id": 1,
"children": [
{
"id": 1
},
{
"id": 2
}
],
"basic": "parent-basic-1"
},
{
"id": 2,
"children": [
{
"id": 3
},
{
"id": 4
}
],
"basic": "parent-basic-2"
}
]
Now my problem with Interface-Projection is, that it will not just load the expected properties, but all properties, but jackson will only serialize those that the Interface provides, cause it uses the Class/Interface-Definition.
Parent loaded: (sql log; see line 4, detail information is loaded)
select
parent0_.id as id1_1_,
parent0_.basic as basic2_1_,
parent0_.detail as detail3_1_
from
parent parent0_
Also Interface Projection seems to be really slow (see this Stackoverflow question) and i still would have to unpack the children cause they are given as [{id:1},{id:2}] but i really need [1,2]. I know i can do this with #JsonIdentityReference(alwaysAsId = true) but thats just a workaround.
Also I'm abit confused why the data is loaded in n+1 queries - 1 for the parents, and another n (where n is the number of parents) for each parents childs:
select
parent0_.id as id1_1_,
parent0_.basic as basic2_1_,
parent0_.detail as detail3_1_
from
parent parent0_
select
children0_.parent_id as parent_i1_2_0_,
children0_.children_id as children2_2_0_,
child1_.id as id1_0_1_,
child1_.basic as basic2_0_1_,
child1_.detail as detail3_0_1_
from
parent_children children0_
inner join
child child1_
on children0_.children_id=child1_.id
where
children0_.parent_id=?
//... omitting further child queries
I have tried #OneToMany(fetch=FetchType.LAZY) and #Fetch(FetchType.JOINED) - both give the same result as above.
So the main question is: Is there any way to achive projection with Spring Boot for nested entities, so that only the needed data is loaded in as little as possible queries and in a best case scenario I can adjust it so that instead of having to load List children i can just load List childIds (maybe through a Jpa query that groups the joined rows by parentid and lets be extract needed data from the Child?).
Im using Hibernate and an In-Memory Database.
Thanks in regards for any answer or tip!
Edit: To clarify: I'm not trying to find a way to serialize the data in the wanted format - this i already can achive. The main focus is on only loading the neccessary information from the database.
this will always fetch the children but could give you the result you want.
public interface SimpleParentProjection {
String getBasic();
String getDetail();
#Value("#{T(SimpleParentProjection).toId(target.getChildren())}")
String[] getChildren();
static String[] toId(Set<Child> childSet) {
return childSet.stream().map(c -> String.valueOf(c.getId())).toArray(String[]::new);
}
}

Sending POST request with Postman with #DBref

I want to send a POST request with Postman that creates a Purchase object and save it in the database.
My class Purchase:
#Document(collection = "purchases")
public class Purchase {
#Id
private String id;
#DBRef
private User buyer;
#DBRef
private List<File> FilesToPurchase;
private Long timestamp;
public Purchase() { }
public Purchase(User buyer, List<File> filesToPurchase) {
this.buyer = buyer;
FilesToPurchase = filesToPurchase;
}
// Getters and setters not posted here.
I want to insert in the database a new purchase done by an already existing User "buyer" who wants to purchase a list of already exsting Files "FilesToPurchase".
I have in my controller a POST function that receives a Purchase object using the annotation #RequestBody, but so far I've got NullPointerExceptions because of the empty Purchase object received.
I don't know how to handle #DBRef annotation. In Postman I try sending a JSON like this:
{
"buyer": {
"$ref":"users",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a2')"
},
"FilesToPurchase": [
{ "$ref":"files",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a5')"
}
]
}
Rename field "FilesToPurchase" and setter to "filesToPurchase" to match java conventions and try this
{ "buyer": { "id" : "5bb5d6634e5a7b2bea75d4a2" }, "filesToPurchase": [ { "id" : "5bb5d6634e5a7b2bea75d4a5" } ] }
By marking controller parameter with #RequestBody you ask Spring to deserialize input json to java object(Jackson ObjectMapper is used by default). It will not automaticly populate the whole #Dbref field, you should do it yourself by querying mongo if you want, however the only field you need in referred object to save object that reffers it is 'id'.

Error to convert objects using #SqlResultMapping and #ConstructorResult to NamedNativeQuery

guys.
I'm facing this problem for a day and I couldn't find a solution.
I have this scenario:
Spring Boot
JPA 2.1
Hibernate
I'm using Repository
The app is a JSON REST service
and this code, my entity Ticket.java:
#Entity
#Table(name = "tickets")
#SqlResultSetMapping(
name="SummaryReport",
classes=#ConstructorResult(
targetClass=ValidatedTicketsVO.class,
columns={#ColumnResult(name="quantity",
type=BigInteger.class),
#ColumnResult(name="quarter", type=String.class)
}))
#NamedNativeQuery(
name="Ticket.findShowSummaryReport",
query="SELECT COUNT(*) AS quantity, DATE_FORMAT( FROM_UNIXTIME(UNIX_TIMESTAMP(validation_time) - UNIX_TIMESTAMP(validation_time)%(15*60)), \"%d/%m/%Y %H:%i\" ) AS quarter " +
" FROM tickets " +
" WHERE show_id = :showId " +
" GROUP BY quarter " +
" ORDER BY quarter ")
public class Ticket implements Serializable {
I also tried using #EntityResult instead of #ConstructorResult:
#SqlResultSetMapping(
name="SummaryReport",
entities=#EntityResult(
entityClass=ValidatedTicketsVO.class,
fields={#FieldResult(name="quantity", column="quantity"),
#FieldResult(name="quarter", column="quarter")
}))
but I got the same error.
and my POJO / VO class that query result has to be mapped to:
import java.math.BigInteger;
public class ValidatedTicketsVO {
private BigInteger quantity;
private String quarter;
public ValidatedTicketsVO(BigInteger quantity, String timeQuarter) {
super();
this.quantity = quantity;
this.quarter = timeQuarter;
}
[ getters and setters ]
}
PS: the field quantity was initially defined as Integer but as I got a [java.math.BigInteger] exception I changed it to Long and now to BigInteger to see if it solves my problem, but stills the same.
And here is my Repository method:
List<ValidatedTicketsVO> findShowSummaryReport(#Param("showId") Long showId);
The query is well executed and I can't see any other log rather than the query itself. I get no exception in the log, but I get this response when I try to execute it through Postman:
{
"code": 5001,
"description": "Unexpected server error",
"timestamp": 1499782989890,
"errors": 1,
"fieldErrors": [
{
"field": "",
"message": "No converter found capable of converting from type [java.math.BigInteger] to type [br.com.company.validator.model.entity.vo.ValidatedTicketsVO]"
}
]
}
When I debug it on Eclipse the exception occurs when I call this method:
List<ValidatedTicketsVO> list = repository.findShowSummaryReport(showId);
Can someone help me, please?

Resources