Mapping flat DB documents to nested classes in Morphia - java

I am porting a Java application that used to use Jongo to communicate with MongoDB to Morphia + new MongoDB Java Driver.
In the database there are flat documents like:
{
"_id" : ObjectId("56c2e5b9b9b6e9753d4f11de"),
"field1" : "foo",
"field2" : "bar",
"field3" : NumberLong("1455613369030"),
"field4" : NumberLong("1455613369030"),
"field5" : NumberLong(58),
"field6" : NumberLong(1)
}
And there are not flat entity classes to store those documents in Java app, annotated using jackson to be usable with Jongo:
public class MyPOJOClass {
public static final String COLLECTION_NAME = "foobar";
#JsonProperty(value = "_id")
private String id;
#JsonUnwrapped
private FooBar foobar;
#JsonUnwrapped
private MyLongs longs;
// Constructor and other methods
public static class FooBar {
#JsonProperty(value = "field1")
private String foo;
#JsonProperty(value = "field2")
private String bar;
// Consteructor and other methods…
}
public static class MyLongs {
#JsonProperty(value = "field3")
private long first;
#JsonProperty(value = "field4")
private long second;
// etc…
}
}
Can I somehow port this exact structure to Morphia as is, without flattening the entity classes or expanding documents (so that foo and bar fields are in one embedded document and LongNumber fields in another document)?
I have not found any examples of the #Embedded annotation (the only one that looks relevant and gives some hope) to do such a thing. I want to end up with something like:
#Entity(MyPOJOClass.COLLECTION_NAME)
public class MyPOJOClass {
public static final String COLLECTION_NAME = "foobar";
#Id
private ObjectId id;
#Embedded(_SOME_MAGIC_?)
private FooBar foobar;
#Embedded(_SOME_MAGIC_?)
private MyLongs longs;
// Constructor and other methods
#Embedded(_SOME_MAGIC_?)
public static class FooBar {
#Property("field1")
private String foo;
#Property("field2")
private String bar;
// Consteructor and other methods…
}
#Embedded(_SOME_MAGIC_?)
public static class MyLongs {
#Property("field3")
private long first;
#Property("field4")
private long second;
// etc…
}
}

Related

Deserializing complex JSON response using Jackson

I am developing my web application backend using Spring. In particular, my application manages data on soccer teams and their players.
My application interacts with a third party REST API to fetch team and player data.
As for the teams, I created a Team entity as follows:
#Data
#Table(name = "team")
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
private Long id;
private String name;
private String logoUrl;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "team")
private Set<Player> players;
}
The response that comes to me from the API, however, has a particular structure and contains an array of Teams in the "response" node.
Here is the structure of the response:
{
"get":"teams",
"parameters":{
"league":"135",
"season":"2020"
},
"errors":[
],
"results":20,
"paging":{
"current":1,
"total":1
},
"response":[
{
"team":{
"id":487,
"name":"Lazio",
"country":"Italy",
"founded":1900,
"national":false,
"logo":"https:\/\/media.api-sports.io\/football\/teams\/487.png"
},
"venue":{
"id":910,
"name":"Stadio Olimpico",
"address":"Viale dei Gladiatori, 2 \/ Via del Foro Italico",
"city":"Roma",
"capacity":68530,
"surface":"grass",
"image":"https:\/\/media.api-sports.io\/football\/venues\/910.png"
}
},
{
"team":{
"id":488,
"name":"Sassuolo",
"country":"Italy",
"founded":1922,
"national":false,
"logo":"https:\/\/media.api-sports.io\/football\/teams\/488.png"
},
"venue":{
"id":935,
"name":"MAPEI Stadium - Citt\u00e0 del Tricolore",
"address":"Piazza Azzuri d&apos;Italia, 1",
"city":"Reggio nell&apos;Emilia",
"capacity":23717,
"surface":"grass",
"image":"https:\/\/media.api-sports.io\/football\/venues\/935.png"
}
},
... // Other team objects
]
}
How can I parse the answer to get a List<Team> using the Jackson library?
You should create classes for Jackson that match result structure then convert instances of those classes to your Team class. Using same class for JPA entity and for Jackson deserialization is a bad idea.
There are online services that allow generating classes like this. For example this one https://json2csharp.com/json-to-pojo generated classes like this:
// import com.fasterxml.jackson.databind.ObjectMapper; // version 2.11.1
// import com.fasterxml.jackson.annotation.JsonProperty; // version 2.11.1
/* ObjectMapper om = new ObjectMapper();
Root root = om.readValue(myJsonString), Root.class); */
public class Parameters{
public String league;
public String season;
}
public class Paging{
public int current;
public int total;
}
public class Team{
public int id;
public String name;
public String country;
public int founded;
public boolean national;
public String logo;
}
public class Venue{
public int id;
public String name;
public String address;
public String city;
public int capacity;
public String surface;
public String image;
}
public class Response{
public Team team;
public Venue venue;
}
public class Root{
public String get;
public Parameters parameters;
public List<Object> errors;
public int results;
public Paging paging;
public List<Response> response;
}
As #Sankozi said you can modelize your java pojos for json deserialization.
Then use an ObjectMapper for deserialization like :
ObjectMapper mapper = new ObjectMapper();
CollectionType javaType = mapper.getTypeFactory()
.constructCollectionType(List.class, Response.class);
List<Response> asList = mapper.readValue(jsonArray, javaType);
List<Team> teams = asList.stream()
.flatMap(response -> response.getTeam())
.collect(Collectors.toList());

MapStruct mapping on objects of type List

I am trying to use MapStruct for a structure similar to the following:
#Data
public class ClassAEntity {
private int id;
private String name;
private String numT;
private List<ClassBEntity) bs;
}
#Data
public class ClassBEntity {
private int id;
private String name;
private String numT;
private List<Other> oc;
}
#Data
public class ClassA {
private int id;
private String name;
private List<ClassB) bs;
}
#Data
public class ClassB {
private int id;
private String name;
private List<Other> oc;
}
In the interface I have added the following mapping:
ClassAEntity map(ClassA classA, String numT)
I get a warning because it can't map numT to classBEntity.numT and I can't add it with #Mapping in the following way:
#Mapping(source = "numT", target = "bs[].numT")
On the other hand I need to ignore the parameter oc of classBEntity because "Other" object contains classAEntity and forms a cyclic object. (because I use oneToMany JPA). I have tried the following:
#Mapping(target = "bs[].oc", ignore = true)
Thank you for your help
MapStruct does not support defining nested mappings for collections. You will have to define more explicit methods.
For example to map numT into bs[].numT and ignore bs[].oc you'll need to do something like:
#Mapper
public MyMapper {
default ClassAEntity map(ClassA classA, String numT) {
return map(classA, numT, numT);
}
ClassAEntity map(ClassA classA, String numT, #Context String numT);
#AfterMapping
default void setNumTOnClassBEntity(#MappingTarget ClassBEntity classB, #Context String numT) {
classB.setNumT(numT);
}
#Mapping(target = "oc", ignore = "true")
ClassBEntity map(ClassB classB);
}

Rewrite code to get only several variables

I have this code which I would like to use to translate keys and return data to front end:
#GetMapping("pages")
public Page<ContractDTO> pagxes(#RequestParam(value = "page") int page, #RequestParam(value = "size") int size) {
return contractService.findAll(page, size)
//.map(mapper::toDTO);
.map(g -> new ContractDTO(g.getName(), getMerchantName(g.getMerchant_id())));
}
private String getMerchantName(int id) {
Optional<Merchants> obj = merchantService.findById(id);
return obj.get().getName();
}
DTO :
public class ContractDTO {
private Integer id;
.....
private Integer acquirer_id;
private Integer terminal_id;
private String merchant_id;
......
}
How I can rewrite this code .map(g -> new ContractDTO(g.getName(), getMerchantName(g.getMerchant_id()))); to translate from int to String using getMerchantName(int id) only terminal_id and merchant_id and all other variables not to be translated?
I can create constructor in ContractDTO but the code will be huge. Is there some other way?
Error:
The method builder() is undefined for the type ContractDTO
In your case because you want to avoid multiple constructors, You can use a builder design pattern, by using lombok library, it can be more easier, so you can just annotate your class of ContractDTO with this library annotation, and you have every thing to go :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
class ContractDTO {
private Integer id;
private String name;
private Integer acquirerId;
private Integer terminalId;
private String merchantId;
}
then your code can be :
...
.map(g -> ContractDTO.builder()
.name(g.getName())
.merchantName(g.getMerchantId())
.build()
)....

JPA AttributeConverter search predicate in specification

I am trying to have a class that has a certain list of objects (specified by another class) persisted in the database as a string (use JPA Converter - all good).
And then I want to use Specification to search inside that string.
What is the best way to create the predicates? I don't seem to understand the connection bettween the AttributeConverter and the Expression in the Specification.
The parent class:
#Entity #Table
public class A {
#Column #Id #GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column
private String name;
#Enumerated(EnumType.STRING)
private SomeType type;
#Column(length=1000) #Convert(converter = BConverter.class)
private List<B> bList;
private Integer no;
}
The listed object class:
public class B{
private String type;
private Integer quantity;
}
The converter:
#Converter
public class BConverter implements AttributeConverter<List<B>, String> {
private static final String SEPARATOR = ":";
private static final String LIST_SEPARATOR = ";";
#Override public String convertToDatabaseColumn(List<B> bList) {
return bList.stream().map(e->convertToString(e)).collect(Collectors.joining(LIST_SEPARATOR));
}
#Override public List<B> convertToEntityAttribute(String str) {
if(str==null || str.isEmpty() ) return null;
return Arrays.stream(str.split(LIST_SEPARATOR)).map(e->convertFromString(e)).collect(Collectors.toList());
}
private String convertToString(B b){
if(entity==null) return null;
return b.getType().toString() +SEPARATOR+ b.getQuantity().toString();
}
private B convertFromString(String subStr){
if(subStr==null || subStr.isEmpty() ) return null;
String[] pair = subStr.split(SEPARATOR);
return new B(pair[0],Integer.valueOf(pair[1]));
}
}
In the database should look something like:
Table A:
id: 1;
name: "Some Name";
type: "THISTYPE";
blist: "TYPE1:11;TYPE2:22";
no: 0;
id: 2;
name: "Other Name";
type: "THISTYPE";
blist: "TYPE1:45;TYPE2:56";
no: 12;
I would then like to create Specifications to search over this table for the attributes inside the bList.
For example, search by an entity that contains a B object where type=TYPE1 and a quantity>=30.
public static Specification<A> customSpecification(String type, Integer value) {
return (root, query, builder) -> ///?????????
}
Is there a way to use such specifications where the DB attribute is a String but JAVA only sees the objects?

Spring Data Couchbase - CrudRepository

I am trying to retrieve a document from couchbase using CrudRepository interface. The document is something similar to below and has name CUSTOMER::52663829988929
{ "DATA": {
"FNAME": "Greg",
"LNAME": "CLARKE" }, "KEY": "CUSTOMER::52663829988929" }
The related class is named com.mycompany.xyz.Customer. I created a repository using the below code :
public interface CustomerRepository extends CrudRepository<Customer, String> {
}
Code for Customer Class -
public class Customer{
private static final long serialVersionUID = 1L;
#Expose #SerializedName("KEY") private String key;
#Expose #SerializedName("DATA") private Data data;
public Customer() {
}
// getters and setters
}
Code for Data class -
public class Data {
private static final long serialVersionUID = 1L;
#Expose #SerializedName("FNAME") private String fname;
#Expose #SerializedName("LNAME") private String lname;
// getters and setters
}
When I execute the below code , I am getting an empty Customer object -
customerRepository.findOne("CUSTOMER::52663829988929")
Please can you let me know what I'm missing.
EDIT:
When I looked at the documents in couchbase , it does not have _class attribute. Is this mandatory? Can't Spring Data's CRUDRepo be used to fetch documents that doesn't have _class attribute?

Categories

Resources