Column lost their value and turned into null after using update function - database

I am currently implementing CRUD functions but when I try the update function, the database added new entity instead of editing the existing one.
My database consist of 2 models "Questions" and "Answers". One "question" can have multiple "answers" but one "answer" can only linked to one "question".
My test run was adding Question content and an array of Answer contents. Checking the database, both of them are linked together through a column called "question_id" in Answer table. But when I am update the body of the Question (both the content of Question and Answer inside Question body), the database doesn't know their original "question_id" anymore.
I felt like I did wrong somewhere. Any ideas?
POST Question and Answer to database
PUT Question and Answer to database
Question model
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String content;
#OneToMany(mappedBy = "question")
#Cascade(CascadeType.ALL)
#Fetch(FetchMode.JOIN)
private Set<Answer> answers;
Answer model
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String content;
#ManyToOne
#JsonIgnore
private Question question;
private boolean isCorrectAnswer;
QuestionService
#Transactional
public int addQuestion(Question question){
for (Answer answer: question.getAnswers()) {
answer.setQuestion(question);
}
int id = (Integer) sessionFactory.getCurrentSession().save(question);
return id;
}
#Transactional
public void updateQuestion(Question question){
sessionFactory.getCurrentSession().saveOrUpdate(question);
}
QuestionController
#RequestMapping(path = "/questions", method = RequestMethod.PUT)
public int updateQuestion(#RequestBody Question question){
questionService.updateQuestion(question);
return question.getId();
}

My advice is to separate persistent classes (used by Hibernate) and classes to serialize/deserialize JSON. Better to use a separate DTO class for each persistent class — Question -> QuestionDto And to convert between them.
To PUT Question
Use this JSON
{
"id": 1,
"answers": [
{
"id" : 100,
"content": "this is an updated answer"
},
{
"content": "this is a new answer"
}
]
}
Do this
for (Answer answer: question.getAnswers()) {
answer.setQuestion(question);
}
And use session.merge().

When entering the update method you supply Question as argument.
But this Question is an unmanaged entity because you built it in the controller outside of any transaction, so when you call saveOrUpdate() what happens is that hibernate thinks this is a new entity and save it accrodingly with a new autogenerated id.
what you need to do is retrieve the entity in the transactional block with a query, that way the entity is already managed and will be dirty checked, thus creating the correct update statement
#Transactional
public void updateQuestion(//Parameter for the query and updated field){
Query query = sessionFactory.getCurrentSession().createQuery("from Question where etc...);
Question question = query.uniqueResult();
question.setContent(//Your updated content);
sessionFactory.getCurrentSession().saveOrUpdate(question);
}

Solved by myself by fixing in QuestionService
First
#Autowire
private QuestionService questionService;
Then instead of editing only Question like this
public Question updateQuestion(Question question){
sessionFactory.getCurrentSession().saveOrUpdate(question);
return question;
}
I am using updateAnswer from AnswerService into this update so everytime I wanted to update Question or Answer or both, Answer can known their updated content and their question_id. This will solve the problem as my updated Answer doesn't know their QuestionID
public Question updateQuestion(Question question){
Set<Answer> answers = question.getAnswers();
for (Answer a : answers) {
a.setQuestion(question);
questionService.updateAnswer(a);
}
question.setAnswers(answers);
sessionFactory.getCurrentSession().saveOrUpdate(question);
return question;
}
So overall, this update function update both Question and Answer, the naming scheme does not fit so I can improve this, something like QuestionAnswerService

Related

Spring data JPA inserting null into column error even though I POST a value?

I want to save both child and parent entities whenever a POST call is made. I have an Item entity with a one to one mapping to a parent table Attribute:
#Entity
#Table(name="Item")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Item
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="attr_id")
private Attribute attribute;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval=true)
private List<ValueSet> valueSets = new ArrayList<>();
// Other fields, getters, setters, overriding equals and hashcode using Objects.equals() and Objects.hashCode() for all the fields, helper for adding and removing ValueSet
}
The Attribute entity looks like this:
#Entity
#Table(name="Attribute")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Attribute
{
#Id
#Column(name="id")
private Long id;
// Other fields, getters, setters, NOT overriding equals hashCode
}
Whenever an Item gets saved I need the Attribute to get saved as well. I've my postman sending JSON POST data as follows:
{
"attribute":{
"id":"6"
},
"valueSets":[
{
"value":"basic"
}
]
}
My handler looks like this:
#PostMapping("/item")
public void postItems(#RequestBody Item item)
{
itemRepository.save(item);
}
ItemRepository is just a one liner with #Repository annotation:
public interface ItemRepository extends CrudRepository<Item, Long>
When I try to save the Item I run into - Cannot insert the value NULL into column 'attr_id', table 'Item'; column does not allow nulls. INSERT fails.
I can't figure out why is it unable to take the id value of 6 that I am supplying as part of my POST invocation. The id value 6 already exists on the Attribute table. I have also tried making the relationship bi-directional using mappedBy and CASCADE.ALL but still get the same error.
Any thoughts/suggestions on what I'm messing/missing? Also, is there a better approach to handle nested entities? Should I try to save each of them individually? In that case can the #RequestBody still be the parent entity?
I have built an example project, and try to replicate your situation, not successful. I am able to insert the "item" without any issue.
I placed the project under this repository https://github.com/hepoiko/user-5483731-1
Hope this help you to troubleshooting further or let me know If I miss anything in there.

How to deal with returning a large List of items in Spring?

This is just a theoretical question that I can't stop thinking about and I am actually wondering how would one solve it
I am currently working on some sort a forum website, and I have a Post entity which looks like this:
#Entity
#Table(name="post")
public class Post extends Likable {
#Id
#GeneratedValue
#Column
private Long id;
#Column(nullable = false)
private String title;
#Column
#Lob
private String text;
#ManyToOne
private Account author;
#OneToMany(cascade = CascadeType.ALL)
List<Comment> comments;
public Post(){}
//getters and setters omitted
}
Now as you see, I have a field called comments, and when I load the post in my front end, I request the comments of the current post and then write them out in the post. And that works great.
But, this is for a post which contains like 20 comments. A array of 20 elements doesn't seem that bad. But what if the post contains like 2000 comments, or even more, like 20000 comments, how do you handle that? Also, is there a maximum size for List? Or a point where it's safe to say that you need to implement a different solution?
Again, this is just a theoretical question, and not something I will have to worry about right now since this is just a side project to learn Spring for me, but I am just wondering.

Load list of items in objectify

I have Question, Like and Hashtag entities. Also there is one to many relationship between Like and Question entities. I am using google cloud endpoints and my problem begins here. In my list method, I return 20 question as json. But for each question object in query I have to check if user is already liked the question and also fetch related hashtags that belongs to the question. How can I do the same operation by key only batch query. Otherwise, I do
ofy().load().type(Like.class)
.filter("questionRef =", questionKey)
.filter("accountRef =", accountKey).first().now();
for each object.
Like entity
#Entity
#Cache
public class Like {
#Id
#Getter
protected Long id;
#Index
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Ref<Account> accountRef;
#Index
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Ref<Question> questionRef;
#Index
#Getter
protected Date createdAt;
Like() {
}
Like(Key<Account> accountKey) {
this.accountRef = Ref.create(accountKey);
this.createdAt = new Date();
}
}
Hashtag entity
#Entity
#Cache
public class Hashtag implements Model<Hashtag> {
#Id
#Getter
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Long id;
#Index
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Ref<Question> questionRef;
#Index
#Getter
#Setter
private String text;
private Hashtag() {
}
private Hashtag(Builder builder) {
this.questionRef = builder.questionRef;
this.text = builder.text;
}
}
There are several parts to this question.
First, hashtags: Just store hashtags in the Question as an indexed list property. Easy.
Second, likes: There are a couple ways to do this efficiently.
One is to create a Like entity with a natural key of "account:question" (use the stringified websafe key). This way you can do a batch get by key for all the {user,question} tuples. Some will be absent, some will be present. Reasonably efficient if you're only concerned about 20 questions, especially if you #Cache the Like.
Another is to create a separate Relation Index Entity that tracks all the likes of a user and just load those up each time. You can put 5k items in any list property, which means you'll need to juggle multiple entities when a user likes more than 5k things. But it's easy to load them all up with a single ancestor query. The RIE will need to be #Parented by the User.
On a separate note - don't call fields thingRef. It's just a thing. The data in the database is just a key. You can interchange Ref<?>, Key<?>, and the native low-level Key. Type information doesn't belong in database names.
I am not sure if you can change the structure of your entities. If the answer is no, then there is no option other than the approach you have taken.
If yes, I would suggest structuring your Question to include the Like and Hashtag information as well.
#Entity
public class Question {
#Id
private long id;
private Set<Key<Account>> likedBy;
private List<String> hashtags;
}
For a question, you can retrieve all the information in one single query. Then collect all the Account keys and make another datastore query to retrieve all the people who have liked the question using keys as below:
Map<Key<Account>, Account> likedByAccounts = ofy().load().keys(accountKeys);

Objectify doesn't always return results

I am using Objectify to store data on Google App Engine's datastore. I have been trying to implement a one-to-many relationship between two classes, but by storing a list of parameterised keys. The method below works perfectly some of the time, but returns an empty array other times - does anyone know why this may be?
It will either return the correct list of CourseYears, or
{
"items": [
]
}
Here is the method:
#ApiMethod(name = "getCourseYears") #ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public ArrayList<CourseYear> getCourseYears(#Named("name") String name){
Course course = ofy().load().type(Course.class).filter("name", name).first().now();
System.out.println(course.getName());
ArrayList<CourseYear> courseYears = new ArrayList<CourseYear>();
for(Key<CourseYear> courseYearKey: course.getCourseYears()){
courseYears.add(ofy().load().type(CourseYear.class).id(courseYearKey.getId()).now());
}
return courseYears;
}
The Course class which stores many CourseYear keys
#Entity
public class Course {
#Id
#Index
private Long courseId;
private String code;
#Index
private String name;
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public List<Key<CourseYear>> getCourseYears() {
return courseYears;
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public void setCourseYears(List<Key<CourseYear>> courseYears) {
this.courseYears = courseYears;
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public void addCourseYear(Key<CourseYear> courseYearRef){
courseYears.add(courseYearRef);
}
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
List<Key<CourseYear>> courseYears = new ArrayList<Key<CourseYear>>();
...
}
I am debugging this on the debug server using the API explorer. I have found that it will generally work at the start for a few times but if I leave and return to the API and try and run it again, it will not start working again after that.
Does anyone have any idea what might be going wrong?
Many thanks.
You might want to reduce the amount of queries you send to the datastore. Try something like this:
Course course = ofy().load().type(Course.class).filter("name", name).first().now();
ArrayList<CourseYear> courseYears = new ArrayList<CourseYear>();
List<Long> courseIds = new List<>();
for(Key<CourseYear> courseYearKey: course.getCourseYears()){
courseIds.add(courseYearKey.getId());
}
Map<Long, Course> courses = ofy().load().type(CourseYear.class).ids(courseIds).list();
// add all courses from map to you courseYears list
I also strongly recommend a change in your data structure / entities:
In your CourseYears add a property Ref<Course> courseRef with the parent Course and make it indexed (#Index). Then query by
ofy().load().type(CourseYear.class).filter("courseRef", yourCourseRef).list();
This way you'll only require a single query.
The two most likely candidates are:
Eventual consistency behavior of the high replication datastore. Queries (ie your filter() operation) always run a little behind because indexes propagate through GAE asynchronously. See the GAE docs.
You haven't installed the ObjectifyFilter. Read the setup guide. Recent versions of Objectify throws an error if you haven't installed it, so if you're on the latest version, this isn't it.

Why does this JPA code not update entity in AppEngine local datastore?

So I have the follow entity with the following code to create and update the object. The resulting data after calling the method is that the field Value has the value of 1 in the Datastore running at my local computer. Why? Isn't the object JPA-managed (or attached as described in this article) and supposed to update itself upon em2.close()?
Entity:
#Entity
public class MyObj {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long Id;
public int Value = 0;
}
Code:
public void createAndUpdate() {
Long id = null;
// Line below calls Persistence.createEntityManagerFactory("transactions-optional").createEntityManager()
EntityManager em1 = Database.getEntityManager();
MyObj obj = new MyObj();
obj.Value = 1;
em1.persist(obj);
em1.close();
id = obj.Id;
EntityManager em2 = Database.getEntityManager();
obj = (MyObj) em2.find(MyObj.class, id);
obj.Value = 2;
// NOTE1
em2.close();
}
On the line marked NOTE1 I have tried inserting em2.persist(obj) and em2.merge(obj) (even though from what I've read, that should not be neccessary since find() returns an attached object). None of them helped.
I'm using JPA v1.
As the JPA spec says, you cannot directly update fields of Entity objects, yet in your posted code you do just that (so the persistence provider has no way of knowing that something has changed, and so can't flush those changes to the datastore).
DataNucleus JPA does actually allow you to update the field directly if you enhance the class that does it to be "PersistenceAware".
Solution : use setters to update fields

Resources