Data/Object Consistency in noSQL database - java

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.

Related

Spring Data Elasticsearch: Convert a String to an Object (and vice versa) using ValueConverter and dot-notation

I have kind of a combination-follow up question to [1] and [2].
I have a POJO with a field I want to persist in - and read from - Elasticsearch:
#Document
public class MyPojo {
private String level3;
// getters/setters...
}
For convenience and because the property is also being persisted (flatly) into postgres, the property level3 should be a String, however it should be written into ES as a nested object (because the ES index is defined elsewhere).
The current solution is unsatisfactory:
#Document
#Entity
public class MyPojo {
#Column(name = "level3)
#Field(name = "level3", type = FieldType.Keyword)
#ValueConverter(MyConverter.class)
private String level3;
// getters/setters...
}
with the object path "level1.level2.level3" hardcoded within MyConverter, which converts from Map<String, Object> to String (read) and from String to Map<String, Object> (write). Because we potentially need to do this on multiple fields, this is not a really viable solution.
I'd rather do something like this:
#Document
#Entity
public class MyPojo {
#Column(name = "level3)
#Field(name = "level1.level2.level3", type = FieldType.Keyword)
#ValueConverter(MyConverter2.class)
private String level3;
// getters/setters...
}
which does not work (writing works fine, while reading we get the "null is not a map" error from [2]).
Is this at all possible (if I understood [2] correctly, no)? If not, is there another way to achieve what I want without hardcoding and an extra converter per field?
Can I somehow access the #Field annotation within MyConverter (e.g. the name), or can I somehow supply additional arguments to MyConverter?
[1] Spring data elasticsearch embedded field mapping
[2] Spring Elasticsearch: Adding fields to working POJO class causes IllegalArgumentException: null is not a Map

Can I directly map the data from an external source on it to persist on DB?

I am pretty new in Spring Data JPA and ORM in general. I have the following architectural doubt.
Lets consider this entity class:
#Entity // This tells Hibernate to make a table out of this class
public class Order {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name = "name")
private String fullName;
private String address;
private String product;
#Column(name = "order_date_time")
private String orderDate;
private Double quantity;
// getters, setters
}
This class is mapped on my order database table.
In my application data came from an Excel document that I parse via Apace POI and than I have to persist on the database.
My doubt is: can I directly use this entity class to map an Excel row using Apache POI in order to persist the Excel rows as order table records? Or is better to use another DTO class to read the rows from Excel and than use this DTO to set the field values of my entity class?
An entity class can contain a constructor using fields?
Can I directly use this entity class to map an Excel row using Apache
POI in order to persist the Excel rows as order table records?
Yes you can.
Or is better to use another DTO class to read the rows from Excel and
than use this DTO to set the field values of my entity class?
It's certainly common to have a DTO layer between the two, but it's not required so it's up to you.
An entity class can contain a constructor using fields?
Yes, but at least Hibernate wants a non-private default constructor as well, so remember to create a protected Order() {} (or any visibility modifier besides private) in addition to your parameterized constructor.
I'm not a heavy user of Apache POI, but I do know it's used to manipulate MS files.
So, here are my two cents, in your use case, you can just read it and map directly to the Entity class as it doesn't expose an API to the external world.
However, if you were building a REST/SOAP API, I recommend you put a DTO in between so you don't mistakenly expose things that shouldn't be exposed.
From architectural point of view better to have a DTO class and encapsulate some logic there.
class CsvOrder {
private String fullName;
private String address;
public CsvRecord(String[] record) {
fullName = get(record, FULLNAME_INDEX);
address = get(record, ADDRESS_INDEX);
}
public Order toOrder() {
Order result = new Order();
result.setFullName(fullName);
return result;
}
}
public static <T> T get(T[] arr, int index) {
final T notFound = null;
return index < size(arr) ? arr[index] : notFound;
}
public static <T> int size(T[] array) {
return array == null ? 0 : array.length;
}
You can put a static method toOrder() to OrderServiceMapper, if you want to totally decouple layers
class OrderServiceMapper {
public static Order toOrder(CsvOrder order) {
Order result = new Order();
result.setFullName(order.getFullName());
return result;
}
}
Also, use Integer in place of int for id. Better to use Long everywhere
// This tells Spring to add this class to Hibernate configuration during auto scan
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}

#JsonIgnore field in frontend UI and include it in Jsonb json serialization to store in Postgres

I need a field to be ignored in front end UI, whereas the same field will be calculated in backend and needs to get stored in Postgres DB as a Jsonb object. Other than transforming the value object into a newer one, do we have any feature in Jackon for this use case.
Test.java
public class Test {
private Integer score;
private Date dateValidated = null;
private Boolean consent = false;
private Date dateConsented;
public void setConsent(Boolean consent) {
this.consent = consent;
this.dateConsented = consent ? new Date() : null;
}
}
Based on consent, dateConsented will be set and i don't want this to be set while calling my service. I can use #JsonIgnore for this
Problem
I will store this Test as json object in postgres (Jsonb). So if i use #JsonIgnore dateConsented will be ignored in DB as well. I don't want that to happen. Any suggestions/solution for this?
Just create a resource class for yourself, and convert this class to it. finally return this resource class to frontend UI.Take a look ConverterFactory from spring.

Hibernate Lazy Object With No Relations

I have an Hibernate object as follows:
#Entity
#Table(name="SOME_TABLE")
public class SomeEntity {
private Long id;
private String someInfo;
#Id
#Column(name = "ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "SOME_INFO")
public String getSomeInfo() {
return someInfo;
}
public void setSomeInfo(String someInfo) {
this.someInfo = someInfo;
}
}
When loading the object using the following code:
sessionFactory.getCurrentSession().load(getEntityClass(), id);
The object's fields are not loaded, instead a proxy object is returned, and the actual fields are loaded only when I explicitly call them by their getter method.
To the best of my knowledge, plain fields (primitives, strings) should be loaded eagerly. Why does the fields, which are not relations or Collections are loaded lazily? is there any way to ask Hibernate to load them eagerly?
This is problematic for me as I use this object as the return value of a Spring REST application, and then I get a could not initialize proxy - no Session exception.
The reason you obtain a proxy is because the Session#load contract is permitted to return a proxy as a placeholder without ever querying the database for the specified object. This is also why it's crucial that the provided identifier for which you wish to load exists as you'll run into unexpected ObjectNotFoundException errors later on if so.
What you want to use is Session#get which is guaranteed to query the database and will not return a proxy, thus those basic attributes you mentioned will be eagerly loaded as you would expect.
For example:
final Comment comment = new Comment( "This is a comment" );
comment.setOwner( session.load( Product.class, productId ) );
session.save( comment );
The benefit here is that the Product isn't fully initialized. We create a persistent proxy with the specified productId value and associate it as the owner of the comment. This is sufficient when we persist the new Comment to make the foreign-key relationship occur without having to actually load the state of Product, avoiding unnecessary overhead.

Separate database model from Network model

Im using GreenDAO and Volley. So I have the following problem: When I make a network request I need to parse with GSON so I have a model to represent entities retrieved from server and other model to represent the GreenDAO objects. Is there any way to only have 1 class per model to represent as a GSON and a Class of ORM?
class Product:
#SerializedName("id")
private String id;
#SerializedName("pictures")
private List<Picture> pictures;
get & set
class PersistentProduct:
private Long id;
private List<Picture> pictures;
/** To-many relationship, resolved on first access (and after reset). Changes to to-many relations are not persisted, make changes to the target entity. */
public List<PersistencePicture> getPictures() {
if (pictures == null) {
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
PersistencePictureDao targetDao = daoSession.getPersistencePictureDao();
List<PersistencePicture> picturesNew = targetDao._queryPersistenceProduct_Pictures(id);
synchronized (this) {
if(pictures == null) {
pictures = picturesNew;
}
}
}
return pictures;
}
First I thought to make a Interface, but when you retrieve the data from a DAO the DAO returns the class and not the interface, so I think cannot do in this way, the only solution I found is to make a "ProductUtils" that converts from a "PersistentProduct" to a "Product" and vice versa.
The most elegant way would be to implement a small extension for greendao, so that you can specify the serialized name during schema-creation.
For Example:
de.greenrobot.daogenerator.Property.java:
// in PropertyBuilder append these lines
public PropertyBuilder setSerializedName(String sname) {
// Check the sname on correctness (i.e. not empty, not containing illegal characters)
property.serializedName = sname;
return this;
}
// in Property append these lines
private String serializedName = null;
public boolean isSerialized() {
return serializedName != null;
}
In entity.ftl add this line after line 24 (after package ${entity.javaPackage};):
<#if property.serializedName??>
import com.google.gson.annotations.SerializedName;
</#if>
And after line 55 (after: <#list entity.properties as property>)
<#if property.serializedName??>
#SerializedName("${property.serializedName}")
</#if>
Afterwards you should be able to use you generated greendao-entity for volley with the following restrictions:
If you get a Product over network, nothing is changed in the db, yet. You have to call insertOrReplace().
If you get a Product from db and send it via network some undesired fields might be serialized (i.e. myDao and daoSession)
If you get a Product via network and call insertOrReplace() the "network"-Product will be persisted and a already existing Product will be replaced by it BUT the referenced entities won't get updated or persisted if insertOrReplace() isn't called for each of them!
If you get a Product via network and call insertOrReplace() for every referenced entity toMany-entities that were referenced by the db-Product are still referenced by the updated Product, although they are not listed in the updated Product. You have to call resetPictures() and getPictures() to get the correct list, which will contain all toMany()-entities references by either the original Product stored in DB or the updated Product from network.
Update addressing 2.
To prevent daoSession and myDao from being serialized, you can use the following ExclusionStrategy:
private static class TransientExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> clazz) {
return (clazz.getModifiers() & java.lang.reflect.Modifier.TRANSIENT) != 0;
}
public boolean shouldSkipField(FieldAttributes f) {
return f.hasModifier(java.lang.reflect.Modifier.TRANSIENT);
}
}
Update addressing 1.,3. and 4.
As a fast solution you can add the following method in the KEEP-SECTIONS of your entity:
public void merge(DaoSession s) {
s.insertOrReplace(this);
// do this for all toMany-relations accordingly
for (Picture p : getPictures()) {
s.insertOrReplace(p);
newPics.add(p.getId());
}
resetPictures();
}
This will result in the original entity being updated and attached to the session and dao. Also every Picture that is references by the network-product will be persisted or updated. Pictures reference by the original entity, but not by the network-entity remain untouched and get merged into the list.
This is far from perfect, but it shows where to go and what to do. The next steps would be to do everything that is done in merge() inside one transaction and then to integrate different merge-methods into dao.ftl.
NOTE
The code given in this answer is neither complete nor tested and is meant as a hint on how to solve this. As pointed out above this solution still has some restrictions, that have to be dealt with.

Categories

Resources