Foreign Key update from JSON using ORMLite - java

I am using ORMLite within an Android application along side Gson and currently struggling with the following issue.
Within my app, there are multiple classes that make use of ORMLite/Gson, for simplicity I shall describe the issue using only two.
Say we have a class Product:
#SerializedName("product_id")
#DatabaseTable(tableName = "products")
public class Product {
public Product() {
}
#DatabaseField(id = true)
private int id;
// Generic stuff
#DatabaseField
#SerializedName("product_desc")
private String desc;
#DatabaseField
#SerializedName("in_stock")
private boolean inStock;
#DatabaseField(unique = true)
#SerializedName("product_name")
private String name;
// Issue occurs here
#DatabaseField(foreign = true, foreignAutoRefresh = true)
private Venue venue;
}
and we have a class "Venue`:
#DatabaseTable(tableName = "venues")
public class Venue {
public Venue() {
}
#SerializedName("venueid")
#DatabaseField(id = true)
private int id;
// Other Generic Junk
#DatabaseField
private String desc;
#DatabaseField
private String email;
#DatabaseField
private String fax;
#DatabaseField
#SerializedName("venue_name")
private String name;
#DatabaseField
private String phoneNumber;
}
I use Gson to deserialize Json from a pre-written API and ORMLite populate the database with this data. An issue occurs as the API returns the venue id of the venue each product is associated with (e.g venueid = 1) and not a Venue object.
However, the database is already populated with these venues so venueid = 1 refers to a real venue within the current database.
The trouble is getting ORMLite to understand this and update the Venue object within Product to be that of id = 1!
Can anyone think of a solution?
EDIT:
To better understand my issue, here is some sample Json:
[
{
"productid": 1,
"venueid": 4,
"product_name": "Jack Daniels",
"in_stock": true,
"orders_accepted": true,
}
...
]
As you can see, I get an int for venueid and NOT a Venue object. Is there an easy way to convert it to it's corresponding venue without the large overhead of multiple queries

This is an high level answer based on assumption that:
your API returns you also a Json string for every Venue object or
you can easily get a Json string for every Venue object you have in your DB (sorry I do not know ORMLite at all, even if from its name, I can image what it does ;) ).
So, somehow build a little dictionary like a Map<Integer, String> where the int key is your venue id and value is Json string for that Venue object.
Then, when you get Product Json from API, do a simple string replacement using a regexp.
For example, you should transform:
{
"productid": 1,
"venueid": 4,
"product_name": "Jack Daniels",
"in_stock": true,
"orders_accepted": true,
}
into:
{
"productid":1,
"venue":{
"venueid":4,
"desc":"aDesc",
"email":"aEmail",
"fax":"aFax",
"venue_name":"aname",
"phoneNumber":"aPhonenumber"
},
"product_name":"Jack Daniels",
"in_stock":true,
"orders_accepted":true
}
After string replacement you should be able to parse the updated Json string into your data structure without involving database anymore. Think this like something a "Json lookup".
If you can read Venue from DB, your query cost will be only a "select all" to fill the map, after that string replacement will occur in memory only.

Related

Spring MappingException: Could not determine type

I am building a Single-Page-Webapp for testing-scenario and i am using spring-jpa. I want to use this JSON data-model for my post-request:
{
"id": 1,
"title": "test-title",
"releaseDate": "2021/12/15",
"rating" : {
"stars" : 5,
"comment" : "very exciting"
}
}
If i start my application, i get the following error:
Caused by: org.hibernate.MappingException: Could not determine type for: de.demo.dto.Rating, at table: books, for columns: [org.hibernate.mapping.Column(rating)]
If i am declaring the class "rating" with #Entity and add the field "id", the application is starting without errors (if i am using an #OneToOne annotation). But for the class "Rating" i do not want to use an own data table with an "id". Can everyone help me with my issues? How do i fix this problem?
Class books:
#Getter
#Setter
#Entity
public class Books {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(unique = true, nullable = false)
private int id;
private String title;
private String releaseDate;
private Rating rating;
}
class Rating
#Getter
#Setter
public class Rating {
int stars;
String comment;
}
Thanks!
So seems you want one to one relationship but dont want it to be in another table, best thing comes to my mind is saving that rating as a json object String. So you might need to do cruds with some third party library like GSON:
Gson gson = new Gson(); // Or use new GsonBuilder().create();
MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2

Spring boot mongodb not saving uniqueId

I'm working on a Spring application and I'm using MongoDB as my database. I have a document structure where I am saving the id of another document to use as a reference. This id is an objectId and then save it using
mongoOperations.save(message)
It is using the same objectId as the one I'm saving for reference to create _id field for this newly created document. So my document is like this
{
"_id":{
"$oid":"610a03578c9e4937107b6501"
},
"ConversationId":{
"$oid":"610a03578c9e4937107b6501"
},
"Author":"author",
"Body":"hey there",
"CreatedAt":{
"$date":{
"$numberLong":"1628171556888"
}
}
}
As you can see that both the ids for _id and ConversationId are the same. I have tried saving ConversationId as a string and it still does the same. Not sure what mistake I'm making.
{
"_id":{
"$oid":"610a03578c9e4937107b6501"
},
"ConversationId": "610a03578c9e4937107b6501",
"Author":"author",
"Body":"hey there",
"CreatedAt":{
"$date":{
"$numberLong":"1628171556888"
}
}
}
This is my model class
#Document(collection = "messages")
public class Message {
#Id
private String id;
#Field("ConversationId")
private String conversationId;
#Field("Author")
private String author;
#Field("Body")
private String body;
#Field("CreatedAt")
private Instant createdAt;
}
COnversationId in the above model class id an objectId and I tried saving it as a string as mentioned above so It was set to type String here. I have also tried making it ObjectId and still the same issue persists.
How to make it create a unique _id for each record and not use conversationId as its id.
I think you need to change id to _id. Attaching reference below
#Document(collection = "messages")
public class Message {
private String _id;
#Field("ConversationId")
private String conversationId;
#Field("Author")
private String author;
#Field("Body")
private String body;
#Field("CreatedAt")
private Instant createdAt;
}

Define deserialization type with GSON

I'm using Google Gson to de/serialize my app database to/from JSON. The app uses Room database with a custom class which has more than 20 fields, so for semplicity let's consider an example one. I recently changed the type of a field from String to JSON; Room managed the migration fine - just by creating a converter class and adding a void version migration - but I'm not sure how to correclty handle Gson.
Here is the object class, respectively the old and the new one:
Old exClass.java
#Entity(tableName = "example_class")
public class exClass {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "name")
public String name;
#ColumnInfo(name = "json")
public String json;
}
New exClass.java
#Entity(tableName = "example_class")
#TypeConverters({Converters.class})
public class exClass {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "name")
public String name;
#ColumnInfo(name = "json")
public JSONObject json;
}
Now when trying to restore old database backups (created with the old version of the class) Gson throws an exception, because it's expecting a JSON object for the field "json" but finds a String.
Is there a way to override the type Gson expects without having to create a custom JsonSerializer<exClass>?
Note that the JSONObject is still fine as far as Room is concerned: the converter converts "json" to a String before inserting it into the database and viceversa, so it's only a matter of having Gson correcly parse the "json" field.
Thanks in advance

Using ObjectId as String in Java (Manual reference) with spring-data mongodb

In MongoDB documentation they suggest to use ObjecId for manual references.
please see https://docs.mongodb.com/manual/reference/database-references/#document-references
original_id = ObjectId()
db.places.insert({
"_id": original_id,
"name": "Broadway Center",
"url": "bc.example.net"
})
db.people.insert({
"name": "Erin",
"places_id": original_id,
"url": "bc.example.net/Erin"
})
I'm using spring-data-mongodb and what I'm looking for is to have a People class defined like this:
#Document
public class People {
private String name;
#Reference // or any Annotation to convert an ObjectId to a String
private String placesId;
private String url;
}
How to have a "places_id" as ObjectId in mongoDB but mapped to a String in our POJO ?
I was expecting to have an annotation like #Reference but it seems to not be implemented.
I don't understand why we don't have this kind of annotation in spring-data-mongodb. I don't want to implement an explicit converter like suggested in spring documentation for all documents that use manual references.
Maybe it's not the right approach.
Did I miss something ?
UPDATE :
I like the idea to have a POJO using only String instead of ObjectId. Let's say I've got a class Place like this :
#Document
public class Place {
#Id
private String id;
private String name;
}
place.getId() will be a String but people.getPlaceId() will be an ObjectId. I want to avoid this unnecessary mapping.
The solution would be:
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
public class People {
#Field(targetType = FieldType.OBJECT_ID)
private String placesId;
}
This will map POJO string to ObjectId in MongoDB.
Why don't you leave the field as ObjectId?
#Document
public class People {
private String name;
private ObjectId placesId;
private String url;
}
If you want to query by this field you can do this:
For lists
List<String> ids // the ids as strings
List<ObjectId> objIds = ids .stream()
.map(i -> new ObjectId(i))
.collect(Collectors.toList());
For single String
String id // single id
ObjectId objId = new ObjectId(id);
If you want to make a real reference to an other object in your database, use the #DBRef annotation which is provided by Spring Data.
Your updated code could look like the following:
#Document
public class People {
private String name;
#DBRef
private Place place;
private String url;
}
Spring Data will then automatically map a Place object to your People object. Internally this is done with a reference to the unique ObjectId. Try this code and have a look at your mongo database.
For more information have a look at: MongoDb with java foreign key
I have a solution very simple:
#JsonSerialize(using= ToStringSerializer.class)
private ObjectId brandId;
...
put that on the attribute that is Object Id, and the ObjectId gets and inserts like string

Spring-data-rest jacksonHttpMessageConverter doesn't convert nested entity [duplicate]

My spring-data-rest integration test fails for a simple json request. Consider the below jpa models
Order.java
public class Order {
#Id #GeneratedValue//
private Long id;
#ManyToOne(fetch = FetchType.LAZY)//
private Person creator;
private String type;
public Order(Person creator) {
this.creator = creator;
}
// getters and setters
}
Person.java
ic class Person {
#Id #GeneratedValue private Long id;
#Description("A person's first name") //
private String firstName;
#Description("A person's last name") //
private String lastName;
#Description("A person's siblings") //
#ManyToMany //
private List<Person> siblings = new ArrayList<Person>();
#ManyToOne //
private Person father;
#Description("Timestamp this person object was created") //
private Date created;
#JsonIgnore //
private int age;
private int height, weight;
private Gender gender;
// ... getters and setters
}
In my test I created a person by using personRepository and inited order by passing person
Person creator = new Person();
creator.setFirstName("Joe");
creator.setLastName("Keith");
created.setCreated(new Date());
created.setAge("30");
creator = personRepository.save(creator);
Order order = new Order(creator);
String orderJson = new ObjectMapper().writeValueAsString(order);
mockMvc.perform(post("/orders").content(orderJson).andDoPrint());
Order is created but creator is not associated with the order. Also I want to pass request body as a json object. In this my json object should contain creator as follows
{
"type": "1",
"creator": {
"id": 1,
"firstName": "Joe",
"lastName": "Keith",
"age": 30
}
}
If I send request body with the following json, the call works fine
{
"type": "1",
"creator": "http://localhost/people/1"
}
But I don't want to send the second json. Any idea how to solve the issue. Because already my client is consuming the server response by sending first json. Now I migrated my server to use spring-data-rest. After that all my client code is not working.
How to solve this?
You are correctly associating order with the creator, however the Person is not associated with the orders. You are missing the List<Order> orders field in Person class. Add this, add annotations, add methods for adding order to person and then before sending JSON you should call something like this:
creator.addOrder(order);
order.setCreator(cretr);
Did you try using cascade = CascadeType.ALL in #ManyToOne annotation
public class Order {
#Id #GeneratedValue//
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)//
private Person creator;
private String type;
public Order(Person creator) {
this.creator = creator;
}
// getters and setters
}
Both your Order and Person classes should implement Serializable to properly break them down into and rebuild them from JSON.
There are some ways to solve your problem, but I want give you a hint. You just can save only "id" of your person and get the person by "id" from your database, when you need this.
It solves your problem and it also saves the memory.
I believe you need to do two things to get this work.
Handle the deserialization properly. As you expect Jackson to populate the nested Person object via the constructor you need to annotate this with #JsonCreator. See here:
http://www.cowtowncoder.com/blog/archives/2011/07/entry_457.html
One of more powerful features of Jackson is its ability to use arbitrary >constructors for creating POJO instances, by indicating constructor to use with
#JsonCreator annotation
...........................................
Property-based creators are typically used to pass one or more
obligatory parameters into constructor (either directly or via factory
method). If a property is not found from JSON, null is passed instead
(or, in case of primitives, so-called default value; 0 for ints and so
on).
See also here on why Jackson may not be able to automatically work this out.
https://stackoverflow.com/a/22013603/1356423
Update your JPA mappings. If the associated Person is now populated correctly by the Jackson deserializer then by adding the necessary JPA cascade options to the relationship then both instances should be persisted.
I think then the following should work as expected:
public class Order {
#Id
#GeneratedValue(...)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = cascadeType.ALL)
private Person creator;
private String type;
#JsonCreator
public Order(#JsonProperty("creator") Person creator) {
this.creator = creator;
}
}

Categories