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
Related
I am playing around with SpringBoot lately along with Spring data jpa. Here's the thing. I have two classes Teacher and Course where there exists a OneToMany and ManyToOne relationship between them respectively. Course is a owning side of this relationship as seen below:
#Entity
#JsonSerialize(using = CourseSerializer.class)
#JsonIgnoreProperties(value = { "teacher", "students" }, allowGetters = true)
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter #Setter
private Long id;
#Getter #Setter
private String title;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
#Getter
#Setter
private Teacher teacher;
....
}
And Teacher class looks like this:
#Entity
#JsonIgnoreProperties(value = { "courses" }, allowGetters = true)
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter #Setter
private Long id;
#Getter #Setter
#JsonProperty("first_name")
private String firstName;
#Getter #Setter
#JsonProperty("last_name")
private String lastName;
#OneToMany(mappedBy = "teacher", cascade = CascadeType.ALL /*another problem: , fetch = EAGER*/)
#JsonSerialize(converter = ListCourseConverter.class)
#Getter #Setter
private List<Course> courses = new ArrayList<>();
....
}
Now Course cannot exist without a Teacher (optional=false). I am trying to make a POST call for the course creation this way to /api/course/teacherId:
{
"title": "Java Complete"
}
And I am expecting the following result from the call:
{
"id": 1,
"title": "Java Complete",
"teacher": "<firstName> <lastName>"
}
For this reason, I am using a CourseSerializer which serializes the Teacher object to display just that:
...
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeStringField("title", value.getTitle());
jgen.writeStringField("teacher", value.getTeacher().getFirstName() + " " + value.getTeacher().getLastName());
jgen.writeEndObject();
...
But interestingly, I am seeing "null null" for the teacher field. My Controller -> Service looks like this for the above operation:
CourseController.java
---------------------
#PostMapping("/{teacherId}")
public Course createCourse(#RequestBody Course course, #PathVariable("teacherId") Long teacherId) {
course.setTeacher(new Teacher(teacherId));
return courseService.createCourse(course);
}
CourseService.java
------------------
public Course createCourse(Course course) {
return courseRepository.save(course);
}
So...
Problem 1) While doing course.getTeacher() for the returned object I see only ID field is populated. I have also tried changing the fetch type for the teacher field in Course class, making a findOne(course.getId()) call after the save(...) operation but doesn't work. But interestingly I see the proper result if I make a GET request after the earlier POST call. So, why am I not able to see the complete data in Course during POST since it is mapped with Teacher already?
Problem 2) Delete operation on Course does not work when the courses are set to fetch in EAGER fashion under Teacher class but works fine for LAZY fetch.
First, you don't need to use all those JSON properties if you use DTOs to receive and response.
course.setTeacher(new Teacher(teacherId)); this is not a good practice at all
use one and point to create the course, other to create the teacher.
after that, you can create a put method to update the course with the teacher
#PutMapping("/{courseId}/{teacherId}")
public Course addTeacherToCourse( #PathVariable("courseId") Long courseId,#PathVariable("teacherId") Long teacherId) {
return courseService.addTeacherToCourse(courseId,teacherId);
}
the course service should validate the courseID and teacherID and if they exist then update and save them.
here you can look at my example based on your code.
I'm trying to use lombok with JSON but I have some hiccups
Json is not respecting the order when the name of the variable is
different from #JsonProperty.
(it's probably a problem with the getters)
Another point is that I would like to hide the id of the Object1 in
the generated json
#Data
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PACKAGE)
#NoArgsConstructor(access = AccessLevel.PACKAGE)
#JsonPropertyOrder({ "id", "objectid", "value" })
public class Object1 {
#JsonIgnore
private Long id;
#JsonProperty("objectid")
private Long subid;
#JsonProperty("value")
private String value;
}
Result:
{
"id" : 123, <--- I want to hide
"value" : "...",
"objectid" : 123
}
"this is a small and fictional class"
Thanks,
To solve the output order try to use ObjectMapper like this:
ObjectMapper om = new ObjectMapper();
String jsonString = om.writeValueAsString(myObject1);
System.out.println(jsonString);
To hide the id you can use this jackson annotation:
#JsonIgnoreProperties(value = {"id"})
If you still want to be able to set the id using setter add this member to the annotation:
allowSetters = true
Your code works fine...
What do you want to get in result?
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;
}
}
I have a problem with #JsonIdentityInfo. I'm getting two id in json file.
{
"name" : "Tim",
"#id" : 1, // fasterxml garbage
"id" : 3,
"company" : {
"name" : "Microsoft",
"employees" : [1], // garbage too
"#id" : 2, // fasterxml garbage
"id" : 3
}
}
Here is my entity:
#Entity
#Table(name = "company")
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#OneToMany(mappedBy = "company")
#Cascade(value = CascadeType.ALL)
private Collection<Employee> employees;
How to delete unnecessary Ids?
UPD:
I'm using com.fasterxml.jackson
This is an old question and you probably got the answer by now, but from reading it, it would seem your problem with additional ids comes from the generator you use.
IntSequenceGenerator will have Jackson generates auto-incremented ids, and you specified that you want them as id property (and as you already have an id property, I guess that's why Jackson produces #id). What you want is Jackson to use your existing id property, and for that you simply need to use the PropertyGenerator.
You will also need to use the scope attribute on the annotation as well as use it also on the Employee class, as you have two independent id sequences.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope=Company.class)
public class Company {}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope=Employee.class)
public class Employee {}
This should produce for your example
{
"name" : "Tim",
"id" : 3,
"company" : {
"name" : "Microsoft",
"employees" : [3],
"id" : 3
}
}
You can use JSON annotation like this to ignore them:
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
#JsonIgnoreProperties({"id", "name"})
When creating JSON object, "id" and "name" will not be in the JSON object.
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.