Hibernate the effective way to persist parent-child data - java

I have the following entities and I would like to give some additional thoughts before I make a final decision.
Post
#Entity
public class Post {
#Id
#GeneratedValue
private long id;
private String author;
private String content;
private String title;
#OneToMany
private List<Comment> comments;
// Standard getters and setters...
}
And the entity, which holds child rows
#Entity
public class Comment {
#Id
#GeneratedValue
private long id;
private String author;
private String content;
#ManyToOne
private Post post;
// Standard getters and setters...
}
A post could have a big number of comments, the most of them of course are not changed during the user session. I would like to find out the best way to save data in the following scenarios:
1. Post data is not changed;
2. Post data is changed.
The standard way is to use the following code
Post saved = postRepository.save(post);
But is this approach the most effective when you have only one comment added or changed to the post? Should the approach be different here, namely remove the one to many relationship between Post and Comment entities and treat them separately? Also, I don't like the idea that a post object, which needs to be updated in the database, may contain a large number of comments, which in turn adds additional load on network.

First to make that work you need a cascade in the post entity else it will just update/save the Post entity
#OneToMany(cascade = CascadeType.ALL ,mappedBy = "post")
private List<Comment> comments;
Also hibernate works by dirty checking your entity.
Hibernate during the merge(update) will dirty check your managed entities and generate one update query just for the entities that you changed, so if you don't touch the Post entity and just update one Comment hibernate will generate one update query.
Also this phrase make no sense.
Also, I don't like the idea that a post object, which needs to be
updated in the database, may contain a large number of comments, which
in turn adds additional load on network.
The OneToMany relationship specified in Post is just for hibernate convenience and it's actually optional.
With that you define what is called a bi-directional relationship.
There is nothing in db except for the fk on Comment referencing Post

Related

One To Many Relationship Supporting Reads but not INSERTS

I am using spring boot (verson 2.1.1) to create an application that needs to one-to-many & many-to-one relationship between two model classes with below requirements
The model classes are
#Entity
#Table(name="ORGANIZATIONS")
public class Organization{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
}
#Entity
#Table(name="DEPARTMENTS")
Public class Department{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
//…
}
Requirements
Both organizations and departments should be created by separate respective rest api's.
Through the POST /organizations api we should be able to create an organization without creating departments in the same api call. In fact the api should fail I tried to pass the json element for department as part of the POST /organizations call.
When calling POST /departments I should be able to pass the organization id to associate the newly created department with the organization.
The GET /organizations api call should return the Collection as part of the organization object
The questions are
How do I associate the two model objects ? Do I add #OneToMany in Organization? What attributes do I pass to #OneToMany? Do I need a similar #ManyToOne on the other side - department?
Do I need any special considerations on the REST controllers?
You will need #ManyToOne for persisting in Department only but you most likely will need #OneToMany in Organization for the GET request.
Just make sure, when saving the Department, that you need to:
Fetch from db the organization
Set the fetched organization on the department object
Add the department to the Organization.departments list
Persist the department
For the error handling return a BAD_REQUEST response:
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

Persisting entities in SpringData without the need to fetch the associations

I have recently started on a project that uses Spring Boot. I am still learning some concepts but some things related to data access are bothering me a bit. Let me use an example.
I have a couple of entities:
#Entity
class Book {
#Id
private Long idBook;
private String title;
#ManyToOne
#JoinColumn(name = "idAuthor")
private Author author;
}
#Entity
class Author {
#Id
private Long idAuthor;
private String name;
#OneToMany(fetch = FetchType.LAZY)
private List<Book> books;
}
For the sake of simplicity lets suppose a Book can have only one Author.
The book repository is a simple interface:
public interface BookRepository extends JpaRepository<Book, Long> {}
I also have a DTO for books:
class BookDTO {
private Long idBook;
private String title;
private idAuthor;
}
When a client want to save a book he will send a json like that:
{
"idBook":328,
"title":"The Martian Chronicles",
"idAuthor":56
}
Everytime someone needs to save a Book he will convert the DTO to entity and fetch the Author before saving:
entityBook.setId(dtoBook.getId());
entityBook.setTitle(dtoBook.getTitle());
entityBook.setAuthor(authorRepository.getById(dtoBook.getIdAuthor()));
bookRepository.save(entityBook);
For me it seems a waste of resources as only the idAuthor need to be saved. This is a simple example. The real life situations I am facing are much more complex and can be frustrating sometimes.
I found a solution using the method EntityManager::getReference.
persisting a new object without having to fetch the associations
Hibernate persist entity without fetching association object. just by id
The problem (if I didn't understand it wrong) is: getting a EntityManager reference (injected via #PersistenceContext) outside the Repositories is not a good practice and the conversion from dto to entity is made on a layer before the call to the Repositories.
Is there an alternative way to accomplish this without accessing the EntityManager on upper layers?
You can try setting the author by :
Author currentAuthor = new Author();
currentAuther.setIdAuthor(dtoBook.getIdAuthor());
entityBook.setAuthor(currentAuthor);
This will not create new Author in case there is an existing one with same Primary key. Please check the Cascade type while setting this

How to index a boolean #Field in Hibernate Search if the field is not in the db table

I have a problem with indexing the boolean #field in Hibernate Search, the problem is when the object has changed the rest of the fields are changed as well only the boolean field keeps the old state of the object.
#JsonIgnore
#Field(name = "isWarning", index = Index.YES)
#SortableField(forField = "isWarning")
private boolean isWarning() {
//some logic
}
what is the right way to approach this problem?
I assume this "logic" you mention accesses other entities. You need to tell Hibernate Search that those entities are included in the entity with the isWarning method.
Let's say the isWarning method is defined in an entity called MainEntity, and it accesses data from another entity called SomeOtherEntity.
In SomeOtherEntity, you will have the reverse side of the association:
public class SomeOtherEntity {
#ManyToOne // Or #OneToOne, or whatever
private MainEntity mainEntity;
}
Just add #ContainedIn and you should be good:
public class SomeOtherEntity {
#ManyToOne // Or #OneToOne, or whatever
#ContainedIn
private MainEntity mainEntity;
}
Note that, unfortunately, this can have a significant impact in terms of performance if SomeOtherEntity is frequently updated: Hibernate Search will not be aware of exactly which part of SomeOtherEntity is used in MainEntity, and thus will reindex MainEntity each time SomeOtherEntity changes, even if the changes in SomeOtherEntity don't affect the result of isWarning. A ticket has been filed to address this issue, but it's still pending.

Index Entities without direct relation in Hibernate Search

I'm trying to use Hibernate Search on two Entities, that do not (and must not) share a relation on object-level, however they're connected by a join table that uses their IDs. (legacy)
These are more or less the two Entities:
#Entity
#Indexed
class Person {
#Id
private long id;
#Field
private String name;
....
}
#Entity
#Indexed
class Address {
#Id
private long id;
#Field
private String street;
#Field
private String zip;
....
}
They are connected by their IDs:
#Entity
class Relation {
#Id
private long id;
private long personId;
private long addressId;
}
The goal I'm trying to achieve is finding similar persons that share a similar address via Hibernate Search. This means I'm searching for attributes from both Person and Address.
I guess the easiest way is to "emulate" an #IndexedEmbedded relation which means denormalizing the data and add "street" and "zip" from Address to a Person document. I stumbled upon Hibernate Search Programmatic API, but I'm not sure if that's the right way to go (and how to go on from to there)..
Would this be the proper way of doing things or am I missing something?
If you cannot add this relationship into the model, you will be pretty much out of luck. You are right that you would have to index the Person and corresponding Address data into the same document (this is what #IndexedEmbedded does really). The normal/best way to customize the Document is via a custom (class) bridge. The problem in your case, however, is that you would need access to the current Hibernate Session within the implementation of the custom bridge.
Unless you are using some approach where this Session for example is bound to a ThreadLocal, there won't be a way for you to load the matching Address data for a given Person within the bridge implementation.

#ManyToOne mapping fails to save parent ID

I'm using JPA2 with EclipseLink implementation
![Simple table structure][1]
Here are the two tables which I try to map and the JPA annotations.
public class Story implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Integer id;
#Temporal(TemporalType.TIMESTAMP)
#Column (name="DATE_CREATED")
Date dateCreated;
String title;
String description;
#Column(name="AUTHOR_ID")
Integer authorId;
#Column(name="COUNTRY_ID")
Integer countryId;
private String reviews;
#OneToMany(mappedBy = "story", cascade=CascadeType.ALL)
private List<Tip> tipList;
}
public class Tip implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String description;
private Integer vote;
#ManyToOne (cascade=CascadeType.ALL)
#JoinColumn(name="STORY_ID", referencedColumnName="ID")
private Story story;
}
As a simple example I would like to persist a story and some story related tips in the same transaction.
Here is the section of code which does that:
Story newStory = new Story(title, body, ...);
EntityTransaction transaction = em.getTransaction().begin();
boolean completed = storyService.create(newStory);
//The tips are saved as a List<String>. This methods creates the needed List<Tip> from the Strings
List<Tip> tips = TipUtil.getTipList(tipList);
newStory.setTipList(tips)
transaction.commit();
I have no errors and all the entities are persisted in the database. The problem is that in the tip table the story_id field is always NULL. I can imagine that JPA is unable to get the new id from the story table. What's the correct approach here?
LE
In the current state of the code, the Tip entities are persisted but the country ID remains null.
With JPA, it is always recommended to update the relationship on both the sides in a bi-directional relationship. This is to ensure that the data is consistent in your application layer and nothing to do with the database.
However it is mandatory that you update the owning side of the relationship in a bidirectional relationship.
So, setting/not setting
story.setTipList(tips)
is up to you. But if you want the changes to reflect properly in DB then you mush call
tip.setStory(story)
as Tip is the owning side here, as per your code.
Also your code looks incomplete to me. Reasons is,
the entity returned by storyService.create(newStory) is managed but not the newStory. So just setting newStory.setTipList(tips) will not updated the db
Because you need to update the parent link story in each of your child.
The way its is done is to create a addTip(Tip tip) method in your Story class.
This method does :
tip.setStory(this);
tipList.add(tip);
If you don't need bedirectional approach, you can remove the story field in Tip and it will resolve your problem
Remove the
#Column(name = "STORY_ID")
private Integer storyId;
You are already declaring it in #JoinColumn(name="STORY_ID", referencedColumnName="ID")
That is why you are getting the error Multiple writable mappings exist for the field [tip.STORY_ID]
You should not be using PrimaryKeyJoinColumn, just JoinColumn, but having your complete class would help giving a certain answer.
PrimaryKeyJoinColumn would only be used if the story_id was also the id of the Tip (no id in Tip) and there was a duplicate basic mapping for it. It should rarely be used, and is not required in JPA 2.0 anymore as duplicate id mappings are no longer required.

Categories