Reference existing data when deserializing Java objects? - java

My object graph consists of Hibernate entities. Now most of the objects don't exist in the new database. However some do. So my object is like this:
Class MyObject{
Set<B> bset;
Set<C> cset;
}
The items in bset need to be instantiated and persisted after deserialization. However, the items in cset already exist in the new database, so I don't want new instances created. What is the best way to tell Jackson I know how to find references to these? Right now I am thinking about using a custom serializer / deserializer for cset, which will serialize it by creating an object with the database ID, and then will deserialize it by pulling the appropriate objects out of the database.
This is kind of complicated and I am hoping there is a simpler solution. Any suggestions?

Figured it out. There are three things I needed:
A JsonCreator to take the entityManager, and the id to return an object
#JsonCreator
#IgnoredMethod
public static UiElement findById(#JacksonInject EntityManager entityManager, #JsonProperty("id") int id) {
return entityManager.find(UiElement.class, id);
}
A JsonValue getter to return an object with only the id
#JsonValue
#Transient
public Map<String,Integer> getJsonObject(){
HashMap<String,Integer> map = new HashMap<String,Integer>();
map.put("id", getId());
return map;
}
The entity manager needed to be injected into the ObjectMapper
//entitymanager for creating any static data based entities
InjectableValues injects = new InjectableValues.Std().addValue(EntityManager.class, entityManager);
mapper.setInjectableValues(injects);

Related

How to lookup entity properties in a static codelist for serialization?

I have a spring JPA entity with numeric properties which should be serialized as string values that are the result of a lookup in a code-list.
#Entity
class Test {
String name ;
int status ;
}
Should be serialized by looking up the numeric value in a code-list like so:
{ "name" : "mytest" , "status" : "APPROVED" }
The code-list is implemented as another entity and can be accessed using a spring-boot JPA repository.
The solution I am looking for must be scalable in that
the code-list cannot be loaded from the database again for each serialization or new object
the serialization code must be generic so that it can be re-used in other entities.
This is, other entities also have numeric properties and their own corresponding code-list and repository.
I understand one could either use a custom Jackson serializer, or implement the lookup as part of the entity. However, neither seems to satisfy the conditions above. A custom jackson serializer can only have the repository autowired if I share it or the map using a static field. The static field makes it hard to re-use the serializer implementation for other entities.
Implementing the lookup as part of the entity, say as custom getter, would make the code hard to re-use, especially since the map for the code-list lookup must be shared across instances.
Update: A third strategy would be to add JPA relationships to the code-list in my entities and to expose the value in a custom getter. For the deserialization rather inefficient lookups would be required, though.
This works, but the static map prevents re-using the code.
#Component
public class NewApprovalSerializer extends JsonSerializer<Integer>
{
#Autowired
ApprovalStatusRefRepository repo;
static Map<Integer, String> map = new HashMap<Integer,String>() ;
#PostConstruct
public void init() {
for ( TGoApprovalStatusRef as : repo.findAll() ) {
Integer key = new Integer( as.getApprovalStatusId() );
String val = as.getNameTx();
map.put( key , val );
}
}
public NewApprovalSerializer() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeObject( map.get( value ) );
}
}
I could change the entity like such, but I again have a static map and the code is even harder to re-use in another entity.
#Entity
class Test {
String name ;
#JsonIgnore
int status ;
static Map<Integer, String> map = new HashMap<Integer,String>() ;
public Test() {
... init static map
}
public String getStatus() {
return map.get(this.status);
}
}
What is the standard way to implement a lookup of values uppon serialization (and vice versa in deserialization)?

Spring bean property set using xml configuration from other bean property

I have two bean classes like below
package com.abc;
public class Employee{
private String id;
private String name;
//setters & getters
}
and
package com.cda;
public class EmployeeDTO{
private String id;
private String name;
//setters & getters
}
I want to set the property fields from Employee to EmployeeDTO using spring xml configuration. Where data is coming from some other sources to the Employee Object.
Can you please help me on this scenario.
Use BeanUtils from apache or spring framework instead. Be careful about the argument positioning in these two ways. They are exactly opposite:
org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
OR
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)
this is actually not a task that spring does for you. Spring is more about wiring all depending objects together that work together during runtime. What you need is a mapper like mapstruct or enter link description here.
Lets consider a scenario
Where Employee gets it data from a datasource and now you want to map it to EmployeeDto
In such cases:
You can add a constructor in EmployeeDto which takes Employee as parameter and maps the field
You can use ModelMapper where a simple line like:EmployeeDto employeeDto = modelMapper.map(employee, EmployeeDto.class); will work
Use BeanUtils import org.apache.commons.beanutils.BeanUtils;
EmployeeDto newObject = new EmployeeDto();
BeanUtils.copyProperties(newObject, oldObject); reference
Use Jackson ObjectMapper by the convertValue() method: (not recommended due to performance issues)
ObjectMapper mapper = new ObjectMapper();
Employee employee = ...;
EmployeeDto employeeDto = mapper.convertValue(employee, EmployeeDto.class);

Data/Object Consistency in noSQL database

I got a question when I develop a pretty large project. Say I store an object in no-sql db, like Google Cloud Datastore, but then I add a new field to this class, then once I make a query and get this object, what will be the value of new field? Does it depend on the serializer or DB or programming language?
For example, in java:
public class Car{
private int numOfDoors;
public Car(int nod){
numOfDoors = nod;
}
}
Then I save an object car1 to Datastore, but I update my code after that.
public class Car{
private int numOfDoors;
private Set<String> tags;
private boolean condition;
public Car(int nod, Set<String> tags, boolean cod){
numOfDoors = nod;
this.tags = tags;
condition = cod;
}
public Set<String> getTags(){
return tags;
}
}
What will happen if I call getTags() when I just update the code and call get to an object just fetched from Datastore?
What if tags and condition are not in contructor? Like:
private Set<String> tags = new HashSet<>();
What about delete a field?
Thanks!
In the Defining Data Classes with JDO of the Google Cloud Documentation, specifically the Object Fields and Entity Properties section, it is explained that:
If a datastore entity is loaded into an object and doesn't have a
property for one of the object's fields and the field's type is a
nullable single-value type, the field is set to null. When the object
is saved back to the datastore, the null property becomes set in the
datastore to the null value. If the field is not of a nullable value
type, loading an entity without the corresponding property throws an
exception....
So basically the newly added properties in the modified class will be set to null because the saved entity doesn't have them.

How to save and query dynamic fields in Spring Data MongoDB?

I'm on Spring boot 1.4.x branch and Spring Data MongoDB.
I want to extend a Pojo from HashMap to give it the possibility to save new properties dynamically.
I know I can create a Map<String, Object> properties in the Entry class to save inside it my dynamics values but I don't want to have an inner structure. My goal is to have all fields at the root's entry class to serialize it like that:
{
"id":"12334234234",
"dynamicField1": "dynamicValue1",
"dynamicField2": "dynamicValue2"
}
So I created this Entry class:
#Document
public class Entry extends HashMap<String, Object> {
#Id
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
And the repository like this:
public interface EntryRepository extends MongoRepository<Entry, String> {
}
When I launch my app I have this error:
Error creating bean with name 'entryRepository': Invocation of init method failed; nested exception is org.springframework.data.mapping.model.MappingException: Could not lookup mapping metadata for domain class java.util.HashMap!
Any idea?
TL; DR;
Do not use Java collection/map types as a base class for your entities.
Repositories are not the right tool for your requirement.
Use DBObject with MongoTemplate if you need dynamic top-level properties.
Explanation
Spring Data Repositories are repositories in the DDD sense acting as persistence gateway for your well-defined aggregates. They inspect domain classes to derive the appropriate queries. Spring Data excludes collection and map types from entity analysis, and that's why extending your entity from a Map fails.
Repository query methods for dynamic properties are possible, but it's not the primary use case. You would have to use SpEL queries to express your query:
public interface EntryRepository extends MongoRepository<Entry, String> {
#Query("{ ?0 : ?1 }")
Entry findByDynamicField(String field, Object value);
}
This method does not give you any type safety regarding the predicate value and only an ugly alias for a proper, individual query.
Rather use DBObject with MongoTemplate and its query methods directly:
List<DBObject> result = template.find(new Query(Criteria.where("your_dynamic_field")
.is(theQueryValue)), DBObject.class);
DBObject is a Map that gives you full access to properties without enforcing a pre-defined structure. You can create, read, update and delete DBObjects objects via the Template API.
A last thing
You can declare dynamic properties on a nested level using a Map, if your aggregate root declares some static properties:
#Document
public class Data {
#Id
private String id;
private Map<String, Object> details;
}
Here we can achieve using JSONObject
The entity will be like this
#Document
public class Data {
#Id
private String id;
private JSONObject details;
//getters and setters
}
The POJO will be like this
public class DataDTO {
private String id;
private JSONObject details;
//getters and setters
}
In service
Data formData = new Data();
JSONObject details = dataDTO.getDetails();
details.put("dynamicField1", "dynamicValue1");
details.put("dynamicField2", "dynamicValue2");
formData.setDetails(details);
mongoTemplate.save(formData );
i have done as per my business,refer this code and do it yours. Is this helpful?

gson.toJson() throws StackOverflowError in Servlet

I have list of objects clients
List<Client> clientsList=new ArrayList<Client>();
clientsList=clientDao.GetAllClients();
Entity Client has others list as attributes:
#ManyToOne(optional=false)
private User createdBy;
#ManyToMany(mappedBy = "Clients")
private Set<ClientType> Types=new HashSet();
#ManyToOne(optional=false)
private LeadSource id_LeadSource;
#ManyToOne(optional=false)
private Agencie id_Agencie;
#OneToMany(cascade=CascadeType.ALL,mappedBy="Owner")
private Set<Propertie> properties=new HashSet();
#OneToMany(cascade=CascadeType.ALL,mappedBy="buyer")
private Set<Sale> sales=new HashSet();
#OneToMany(cascade=CascadeType.ALL,mappedBy = "client")
private Set<Rent> Rents=new HashSet();
#OneToMany(cascade=CascadeType.ALL,mappedBy = "clientDoc")
private Set<Document> Docuements=new HashSet();
and when i try to convert list of clients to json format
out.write(new Gson().toJson(clientsList));
i get this error :
java.lang.StackOverflowError
at com.google.gson.stream.JsonWriter.beforeName(JsonWriter.java:603)
at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:401)
at com.google.gson.stream.JsonWriter.value(JsonWriter.java:512)
at com.google.gson.internal.bind.TypeAdapters$8.write(TypeAdapters.java:270)
at com.google.gson.internal.bind.TypeAdapters$8.write(TypeAdapters.java:255)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:113)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:240)
That is because your entities have bidirectional connections. So for example Client has a set of Rents and each rent has a reference to Client. When you try serializing a Client you serialize its Rents and then you have to serialize each Client in Rent and so on. This is what causes the StackOverflowError.
To solve this problem you will have to mark some properties as transient (or use some similar anotation), for example use transient Client in Rent Then any marshalling lib will just ignore this property.
In case of Gson you can do the other way around marking those field you do want to be included in json with #Expose and creating the gson object with:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
P.S. Also, I would like to mention that converting your JPA entity to json and sending it somewhere is generally not a very good idea. I'd recommend creating a DTO(Data Transfer Object) class where you include only the info you need and ideally using only simple types like int, Date, String and so on. If you have questions about this approach you can google for DTO, Data Transfer Object or follow this link: https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm
After some time fighting with this issue, I believe i have a solution.
As #Nestor Sokil explained problem is in unresolved bidirectional connections, and how to represent connections when they are being serialized.
The way to fix that behavior is to "tell" gson how to serialize objects. For that purpose we use Adapters.
By using Adapters we can tell gson how to serialize every property from your Entity class as well as which properties to serialize.
Let Foo and Bar be two entities where Foo has OneToMany relation to Bar and Bar has ManyToOne relation to Foo. We define Bar adapter so when gson serializes Bar, by defining how to serialize Foo from perspective of Bar cyclic referencing will not be possible.
public class BarAdapter implements JsonSerializer<Bar> {
#Override
public JsonElement serialize(Bar bar, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", bar.getId());
jsonObject.addProperty("name", bar.getName());
jsonObject.addProperty("foo_id", bar.getFoo().getId());
return jsonObject;
}
}
Here foo_id is used to represent Foo entity which would be serialized and which would cause our cyclic referencing problem. Now when we use adapter Foo will not be serialized again from Bar only its id will be taken and put in JSON.
Now we have Bar adapter and we can use it to serialize Foo. Here is idea:
public String getSomething() {
//getRelevantFoos() is some method that fetches foos from database, and puts them in list
List<Foo> fooList = getRelevantFoos();
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Bar.class, new BarAdapter());
Gson gson = gsonBuilder.create();
String jsonResponse = gson.toJson(fooList);
return jsonResponse;
}
One more thing to clarify, foo_id is not mandatory and it can be skipped. Purpose of adapter in this example is to serialize Bar and by putting foo_id we showed that Bar can trigger ManyToOne without causing Foo to trigger OneToMany again...
Answer is based on personal experience, therefore feel free to comment, to prove me wrong, to fix mistakes, or to expand answer. Anyhow I hope someone will find this answer useful.

Categories

Resources