Minimize JSON to deserialize and persist complex POJO with JPA/Hibernate - java

I have a fairly complex POJO that I need to deserialize from a JSON string and persist in a MySQL database. The following is a very simplified example class:
#Entity
#Table(name="a")
public class A{
private Long id;
private B b;
private C c;
private D d;
}
#ManyToOne
#JoinColumn(name="b_id")
public B getB(){
return this.b;
}
public void setB(B b){ this.b = b; }
#ManyToOne
#JoinColumn(name="c_id")
public C getC(){
return this.c;
}
public void setC(C c){ this.c = c; }
#ManyToOne
#JoinColumn(name="d_id")
public D getD(){
return this.d;
}
public void setD(D d){ this.d = d; }
Each class B, C, and D also have a number of fields and objects (some with even more required objects and fields) that can not be null according to the database schema, which I can't change. I can deserialize and persist this no problem, but the JSON required to do so is really massive. I only need to persist the deserialized A, so I really just need the _id fields from B, C, and D.
Right now my JSON is something like:
{
"id":1,
"b":{"id":2, ...},
"c":{"id":3, ...},
"d":{"id":4, ...}
}
where I have to fill in all the non-nullable database fields. What I would like to do is read a JSON string like:
{
"id":1,
"b_id":2,
"c_id":3,
"d_id":4
}
and just have Hibernate/JPA update those fields in the database. I think the real tricky part is that other classes/methods in my application will need the entire object hierarchy for reading from the database. The only time I can use just the _id fields is during the deserialization of the JSON. When this is the case, I only need to update the top-most object (A in my example) and some other trivial fields. In a perfect world, I can just throw some annotations on my class to solve this problem, but I haven't found anything capable of doing this.
Is this even possible? If so, can it be done with JPA/Jackson annotations?
Thanks!

If mapping the JSON directly to your entities is akward, I would simply make the translation from the incoming JSON string to your entities explicit via an intermediate object (call it a DTO if you will).
I also have a gut feeling that if you save associated object references (the #ManyToOnes) with only the id populated, Hibernate will in fact save the association correctly (root object here represents A):
{
"id" : 1,
"b" : {
"id" : 2
},
...
}
Make sure you don't cascade the persist operation for the b, c etc. fields.

You should create new classes which define only this fields which you want to deserialize. For example if you want to deserialize only ID your class could looks like this:
class JsonEntity {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonAnySetter
public void setUnknownProperties(String name, String value) {
//do nothing
}
#Override
public String toString() {
return String.valueOf(id);
}
}
In this example annotation JsonAnySetter do the trick. Now, you have to create some class which will be simulating your A class. For example:
class DtoA extends JsonEntity {
private JsonEntity b;
private JsonEntity c;
private JsonEntity d;
public JsonEntity getB() {
return b;
}
public void setB(JsonEntity b) {
this.b = b;
}
public JsonEntity getC() {
return c;
}
public void setC(JsonEntity c) {
this.c = c;
}
public JsonEntity getD() {
return d;
}
public void setD(JsonEntity d) {
this.d = d;
}
#Override
public String toString() {
return "A [id=" + getId() + ", b=" + b + ", c=" + c + ", d=" + d + "]";
}
}
Now when we have new JSON data model we can test it. For example we can parse below JSON:
{
"id":1,
"b":{"id":2, "p1":"v1", "p2":"v2"},
"c":{"id":3, "p3":"v3", "p4":"v4", "p5":"v5"},
"d":{"id":4, "p6":"v6"}
}
Deserialization example:
ObjectMapper objectMapper = new ObjectMapper();
DtoA a = objectMapper.readValue(json, DtoA.class);
System.out.println(a);
Above program prints:
A [id=1, b=2, c=3, d=4]
Now, you have to implement toA() method in DtoA class which could look like this:
public A toA() {
A a = new A(getId());
a.setB(new B(getB().getId()));
a.setC(new C(getC().getId()));
a.setD(new D(getD().getId()));
return a;
}
Let me know whether it works for you.

Related

Creating JSON by combining fields from two classes

I have two classes : class A , class B
class A{
private int F1;
private String F2;
}
class B{
private int F3;
private String F4;
private String F5;
}
I want a JSON like this:
{
"F1": 123
"F2": "ABC"
"F3": 456
"F4": "CDE"
"F5": "FGH"
}
I am using springboot which creates JSON as soon as I return object from #RestController. How can I achieve the above json using these two classes.
Note :
1.) I already know that by using class A extends B , I can achieve
this but I am looking for some spring based method to achieve this
2.) Using #Embeddable in class B & then creating reference in Class A creates
additional tag B in JSON as shown :
{
"F1": 123
"F2": "ABC"
b: {
"F3": 456
"F4": "CDE"
"F5": "FGH"
}
}
How about using jackson #JsonUnwrapped?
http://fasterxml.github.io/jackson-annotations/javadoc/2.0.0/com/fasterxml/jackson/annotation/JsonUnwrapped.html
public class A{
#JsonUnwrapped
private B b;
public User getB() ...
}
Create a delegating class AB:
public final class AB {
private final A a;
private final B b;
public AB(A a, B b) {
this.a = a;
this.b = b;
}
// Delegation methods to A
public int getF1() { return this.a.getF1(); }
public String getF2() { return this.a.getF2(); }
// Delegation methods to B
public int getF3() { return this.b.getF3(); }
public String getF4() { return this.b.getF4(); }
public String getF5() { return this.b.getF5(); }
}

MapStruct map correct instance of object based on target type

How to correct map different classes with same parent
spring DTO jackson objects
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type",
)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = B.class, name = "TypeB"),
#JsonSubTypes.Type(value = C.class, name = "TypeC")
})
abstract class A {
Type type;
String id;
}
class B extends A {
String name;
}
class C extends A {
String description;
}
Entity class contains all fields
class myEntity {
Type type;
String id;
String name;
String description;
}
MapStruct mapper
public abstract class IntegrationMapper {
public A toDto(MyEntity myEntity);
public MyEntity fromDto(A integrationDTO)
}
How I can create different instances B or C into toDto depends on type value ?
I use smth like that
public abstract class IntegrationMapper {
public A toDto(MyEntity myEntity) {
if(myEntity.type == TypeB) {
return toB(myEntity);
} else if (myEntity.type == TypeC) {
return toC(myEntity);
}
}
public MyEntity fromDto(A a) {
if(a instanceOf B) {
return fromDto((B) a);
} else if (a instanceOf C) {
return fromDto((C) a);
}
}
protected B toB(MyEntity myEntity);
protected C toC(MyEntity myEntity);
protected MyEntity fromDto(B c);
protected MyEntity fromDto(C c);
}
But I suspect that it can be done better with ObjectFactory or smth like that
to avoid long if statement and creation new method for each new child of A
If you want to perform the mapping for the fields in B and C you will have to create methods for them. MapStruct is a code generation so it doesn't know anything about the runtime types.
Your current approach is the way to go for what you are looking for. I don't think that using ObjectFactory can help you out. The only way it can help you out is if you want to map MyEntity to the base A. In such case it will generate mapping only between the base and the entity, which is not what you are looking for.
There is an open feature request (#131) that can generate those instance of checks for you.

Not able to save value In Mongo DB from Spring using MongoRepository

I am trying to do addition based on certain input's using Spring & save it in Mongo database.
As I have to do multiple addition :
1.So one way is to manually add the values and set them in bean and save it to the database.
OR
2.Just add them in getter of field & fetch when required.
When tried with second approach, I am not able to save the data in MongoDB
Please find sample code :-
Bean Class :
class Addition {
private double a;
private double b;
private double c;
private double d;
//getters and setters of a & b;
//getter of c;
public double getC() {
return a + b;
}
//getter of d;
public double getD() {
return getC() + a;
}
}
Interface which extends MongoRepository :
#Repository
public interface AdditionRepository extends MongoRepository<Addition, String> {
}
Calling Class :
#Controller
public class Add {
#Autowired
private AdditionRepository additionRepository;
#RequestMapping(value = "/add", method = RequestMethod.GET)
public void addNumbers(){
Addition addition = new Addition();
addition.setA(1.0);
addition.setB(2.0);
System.out.println(addition.getC()); //able to print expected value
System.out.println(addition.getD()); //able to print expected value
additionRepository.save(addition);
}
}
Data Saved in Mongo DB :
{
"_id" : ObjectId("581b229bbcf8c006a0eda4b2"),
"a" : 1.0,
"b" : 2.0,
"c" : 0.0,
"d" : 0.0,
}
Can anybody please let me know, where I am doing wrong, Or any other way of doing this.
The getters are not actually used for persistance. The framework is using the field instead:
"The fields of an object are used to convert to and from fields in the document"
http://docs.spring.io/spring-data/mongodb/docs/1.6.3.RELEASE/reference/html/#mapping-conventions
In your case, you could create a constructor which will take care of the computation:
class Addition {
private double a;
private double b;
private double c;
private double d;
public Addition(double a, double b){
this.a = a;
this.b = b;
this.c = a+b;
this.d = this.c + a;
}
}

JAXB using #XmlJavaTypeAdapter to marshal a subset of a Collection

I've got an entity which contains a collection of a different type of entities. What I want to do is have JAXB marshal only a select subset of the collection, based on some criteria.
#XmlRootElement
#Entity
public class A{
// other fields
#OneToMany(mappedBy = "x", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Collection<B> bees;
#XmlJavaTypeAdapter(BFormatter.class)
public Collection<B> getBees() {
return bees;
}
public void setBees(Collection<B> bees) {
this.bees= bees;
}
}
#XmlRootElement
#Entity
public class B{
// fields
}
public class BFormatter extends XmlAdapter<Collection<B>, Collection<B>>{
#Override
public Collection<B> unmarshal(Collection<B> v) throws Exception {
return v;
}
#Override
public Collection<B> marshal(Collection<B> v) throws Exception {
Collection<B> subset;
// making subset
return subset;
}
}
This results in errors saying "java.util.Collection is an interface, and JAXB can't handle interfaces" and that "java.util.Collection does not have a no-arg default constructor."
What am I doing wrong, and is this even the right way to go about it?
The important thing is that you can't adapt a Collection (an interface) to something JAXB can handle, since it doesn't marshal an ArrayList or some other collection class. It is designed to marshal (bean) classes containing fields that are Lists or similar, which is meant to "disappear", remaining as the mere repetition of its elements. In other words, there's no XML element representing the ArrayList (or whatever) itself.
Therefore, the adapter has to modify the containing element. (See below for alternatives.) The following classes are working; just assemble a Root element and modify the AFormatter according to your design. (The comments refer to the example at
https://jaxb.java.net/tutorial/section_6_2_9-Type-Adapters-XmlJavaTypeAdapter.html#Type%20Adapters:%20XmlJavaTypeAdapter.)
(Most classes should be modified to avoid making fields public, but as it is, it is brief and working.)
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root{ // Training
#XmlElement
private A a; // Brochure
public Root(){}
public A getA(){ return a; }
public void setA( A value ){ a = value; }
}
#XmlJavaTypeAdapter(AFormatter.class)
public class A{ // Brochure
private Collection<B> bees;
public A(){
bees = new ArrayList<>();
}
public Collection<B> getBees() {
if( bees == null ) bees = new ArrayList<>();
return bees;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public class B{ // Course
#XmlElement
private String id;
public B(){}
public String getId(){ return id; }
public void setId( String value ){ id = value; }
}
public class AFormatter extends XmlAdapter<BeeHive, A>{
#Override
public A unmarshal(BeeHive v) throws Exception {
A a = new A();
for( B b: v.beeList ){
a.getBees().add( b );
}
return a;
}
#Override
public BeeHive marshal(A v) throws Exception {
BeeHive beeHive = new BeeHive();
for( B b: v.getBees() ){
if( b.getId().startsWith("a") ) beeHive.beeList.add( b );
}
return beeHive;
}
}
public class BeeHive { // Courses
#XmlElement(name="b")
public List<B> beeList = new ArrayList<B>();
}
Alternatives: It would be quite simple if the regular getter of the B-list would return the ones that should be marshalled. If the application needs to see all, an alternative getter could be added. Or, the class could have a static flag that instructs the getter to return a List to be used for marshalling, or the regular list at other times.

Jackson: Printing Values as Keys in the JSON

Normally what we do in Jackson to print a class as JSON object is to define getter and setter like
public class MyClass
{
private Integer a;
private Integer b;
public myClass(Integer a, Integer b)
{
this.a = a;
this.b = b;
}
#JsonProperty
public Integer getA()
{
return a;
}
#JsonProperty
public Integer getB()
{
return b;
}
public void setA(Integer a)
{
this.a = a;
}
public void setB(Integer b)
{
this.b = b;
}
}
and this will return {"a":1,"b":2}
But can I get output as {1:2} instead of what I am getting before?
In order to achieve that, you need to write own code and pass accordingly. But output you showed is standard json format which you cannot change, but definitely you can change it with code.
If you do not care that output JSON is not valid you can write custom serializer for your POJO class. It could look like this:
class MyClassJsonSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass myClass, JsonGenerator generator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeRaw(myClass.getA() + ":" + myClass.getB());
generator.writeEndObject();
}
}
Using:
#JsonSerialize(using = MyClassJsonSerializer.class)
class MyClass {
....
}
Since now, your POJO should be serialized to desired output.

Categories