Hibernate: Save multiple association issue - java

I have three entities with relations.
public class Site {
#OneToMany
private List<Page> pages;
}
public class Page {
#OneToMany
private List<Article> articles;
#OneToMany
private List<TopArticle> topArticles;
}
public class Article {
#ManyToOne
private Page page;
}
public class TopArticle {
#ManyToOne
#JoinColumn(name = "PageId")
private Page page;
#OneToOne
#JoinColumn(name = "ArticleId")
private Article article;
}
Fields page and arctile in TopArticle have database restrictions - NOT NULL. (Resctrictions are required)
I have to save Site with all associations.
Article article = new Article();
TopArticle topArticle = new TopArticle();
Page page = new Page();
page.getArticles().add(article);
page.getTopArticles().add(topArticle);
Site site = new Site();
site.getPages().add(page);
siteDAO.save(site);
Sometimes it saves site.
But sometimes it throws an error.
com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert the value NULL into column 'ArticleId', table '...TopArticle'; column does not allow nulls. INSERT fails
It seems that after Page is saved, it tries to save associations List articles and List topArticles.
Perhaps, when List articles are saved before List topArticles it works.
In another way it fails.
Questions:
Is an issue in saving properties order in JPA?
How can I enforce Hibernate to save articles before topArticles?
Is there any other solutions?
Thanks

With a bi-directional relationship it is your responsibility to ensure both sides are set correctly on the in-memory model otherwise you will get the error you are seeing.
So you need to call:
article.setPage(page);
And ideally you should encapsulate the operation so the in-memory model is always consistent:
public class Page{
public List<Article> getArticles(){
return Collections.unmodifiableList(articles); //force through add method
}
public void addArticle(Article article){
articles.add(article);
article.setPage(this);
}
}

Related

Why #OneToMany relations generate additional SQL select queries?

#Entity
public class Author {
#OneToMany(cascade = {PERSISTE, MERGE},
mappedBy = "author")
private List<Book> books = new ArrayList<>();
public void addBook(Book book) {
this.books.add(book);
book.setAuthor(this);
}
//other fields
}
#Entity
public class Book{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private Author author;
}
public class BookService {
AuthorRepository repository;
#Transactional
public void saveBooks(){
Book newBook = new Book);
Author author = repository.findByid(1L);
author.addBook(newBook);
}
}
In a database, an author already contains one book.
I create a new book for an author and want to save it.
But when the function addBook is called the existing book is loaded from the database. I see the additional SQL query in logs:
#Query["select book ...."]
How can I fix it?
I don't want to have additional SQL selects
This is just how PersistentBag works - Hibernate loads it when you perform action on list. I would simply replace adding new Book to Author's books list with referencing created Book to existing Author
public class BookService {
BookRepository bookRepository;
AuthorRepository authorRepository;
#Transactional
public void saveBooks() {
Book newBook = new Book();
Author author = authorRepository.findByid(1L);
newBook.setAuthor(author);
bookRepository.save(newBook);
}
}
The reason for this is the basic premise under which JPA works: You load an object graph, you manipulate it, at the end of the transaction JPA makes sure the changes end up in the database.
I see two ways to avoid the select.
reverse the relationship: When the Book references the Author there is no need to load the collection, since it doesn't even exist in the first place. If you actually need the collection you can always use a dedicated query for that.
Drop back to SQL and just execute a SQL insert statement. Of course this can lead to inconsistencies between your database and the 1st level cache of JPA. Make sure you understand how the 1st level cache works and what it is used for.

JPA + #OneToMany + DELETE: Item is not deleted if I access parent later

I got a Deal which can have multiple DealItems.
The DealItems are linked in the Deal with the following JPA annotation:
public class DealEntity extends BasicEntity {
#OneToMany(mappedBy = "deal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DealItemEntity> items;
...
This is the relation inside a DealItem:
public class DealItemEntity extends BasicEntity {
#ManyToOne
#JoinColumn(name = "deal_id", nullable = false)
private DealEntity deal;
...
When I delete a DealItem it is deleted and persited again, when when I access the Deal after the deletion, see here:
public FullDealResponse deleteDealItem(final String dealCode, final long dealItemId) {
DealEntity dealEntity = dealControl.findDealByDealCode(dealCode);
if (dealEntity == null) {
throw new WorkbenchGenericErrorException("Deal not found");
}
DealItemEntity dealItemEntity = dealItemControl.findDealItemByIdAndDealId(dealItemId, dealEntity.getId());
if (dealItemEntity == null) {
throw new WorkbenchGenericErrorException("Deal item not found");
}
// this makes a database DELETE call that is executed after the session is done
dealItemControl.deleteDealItem(dealItemEntity);
// When I remove this and I do not return anything, the deletion works
return this.getFullDealResponse(dealEntity);
}
EDIT:
This is getFullDealResponse() and getFullDealItemResponse():
private FullDealResponse getFullDealResponse(final DealEntity dealEntity) {
FullDealResponse response = new FullDealResponse();
response.setDescription(dealEntity.getDescription());
response.setTitle(dealEntity.getTitle());
response.setDealCode(dealEntity.getDealCode());
response.setCreatedAt(dealEntity.getCreatedAt());
response.setUpdatedAt(dealEntity.getUpdatedAt());
// get related items
List<FullDealItemResponse> itemsResponse = new ArrayList<FullDealItemResponse>();
for (DealItemEntity dealItemEntity : dealEntity.getItems()) {
itemsResponse.add(this.getFullDealItemResponse(dealItemEntity));
}
response.setItems(itemsResponse);
return response;
}
private FullDealItemResponse getFullDealItemResponse(final DealItemEntity dealItemEntity) {
FullDealItemResponse response = new FullDealItemResponse();
response.setId(dealItemEntity.getId());
response.setDescription(dealItemEntity.getDescription());
response.setTitle(dealItemEntity.getTitle());
response.setCreatedAt(dealItemEntity.getCreatedAt());
response.setUpdatedAt(dealItemEntity.getUpdatedAt());
return response;
}
This is deleteDealItem() and delete() function:
public void deleteDealItem(final DealItemEntity dealItemEntity) {
super.delete(DealItemEntity.class, dealItemEntity.getId());
}
protected void delete(final Class<?> type, final Object id) {
Object ref = this.em.getReference(type, id);
this.em.remove(ref);
}
Can this be solved when I switch the CascadeType, and if so, which would be the correct type? Or would I have to iterate over Deal.getItems(), remove the unwanted item, set the new list with Deal.setItems() and update only the Deal so it propagates the deletion?
What is the preferred way to do this?
I have replicated this code locally and verified my explanation
Summary:
Cascade does not have impact. Even if you remove your cascade operation, save each item separately , then when you come to this method, it will not delete your item.
To have same behaviour regardless of deal.getItems initialisation, You will have to delete the dealItem by removing it from deal.getItems in addition to deleting the dealItem directly.
On a bi-directional relationship, you will have to explicitly manage both sides. Exactly the same way, you add the dealItem to deal as well set deal field of dealItem before you save.
Overall Explanation
JPA can have only one representation of a particular item associated with it's session.
It is the foundation for providing Repeatble Read, Dirty Checking etc.
JPA also tracks every object associated with its session and If any of the tracked objects have changes, they will flushed when the transaction committed.
When only deal object (with lazy deaItems collection) and the directly fetched dealItem are the only two entities associated with the session, then JPA has one presentation for each in the session, since there is no conflict, when you delete it, it deletes it via dealItemControl.deleteDealItem the dealItem is deleted
However, once you call deal.getItems, JPA not only manages deal, but also every dealItem associated with the deal object. So when when you delete the dealItemControl.deleteDealItem, JPA has an issue because deal.getItems tells it is not marked for delete. So the delete is not issued.
Reference: JPA QL generated also confirms my explanation
1. With deal.getItems and Queries Generated
#OneToMany(mappedBy = "deal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DealItemEntity> items;
DealEntity dealEntity = dealControl.findDealByDealCode(dealCode);
....
dealItemControl.deleteDealItem(dealItemEntity);
....
dealEntity.getItems()
select deal0_.* from deal deal0_ where deal0_.id=?
select dealitem0_.*
deal1_.*
from
deal_item dealitem0_ inner join deal deal1_ on dealitem0_.deal_id=deal1_.id
where
dealitem0_.id=?
select items0_.* from deal_item items0_ where items0_.deal_id=?
2. Without deal.getItems and Queries Generated
#OneToMany(mappedBy = "deal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DealItemEntity> items;
DealEntity dealEntity = dealControl.findDealByDealCode(dealCode);
....
dealItemControl.deleteDealItem(dealItemEntity);
select deal0_.* from deal deal0_ where deal0_.id=?
select dealitem0_.*
deal1_.*
from
deal_item dealitem0_ inner join deal deal1_ on dealitem0_.deal_id=deal1_.id
where
dealitem0_.id=?
delete from deal_item where id=?

Lazy load doesn't work - I Only need the parent object, not all associations

How do get the object I want, without all of the child associations.
I have my class Site:
#Entity
#Table(name = "Sites")
public class Site {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Id_Site", unique = true, nullable = false)
private long Id_Site;
private String ...;
private boolean ...;
private long ...;
private Date ...;
private Date ...;
private String ...;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Sequence> sequences = new HashSet<>();
#ManyToOne
private ... ...;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<...> ... = new HashSet<>();
#ManyToOne
private ... ...;
public constructor...
public set..
public get..
}
I only need a Site object, without the Sequence Associations.
In my Sequence Table, I have:
#Entity
#Table(name = "Sequences")
public class Sequence {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Id_Sequence", unique = true, nullable = false)
private long Id_Sequence;
private Date ....;
private Date ....;
private String ....;
private String ....;
private String ....;
private int ....;
private int ....;
private double ....;
private double ....;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<TraceSequence> traceSequences = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
private Site site;
public constructor...
public set..
public get..
}
When I use FetchType.Lazy, and call my method:
#Override
public Site findSiteByName(String Name_Site) {
List<Site> sites = entityManager.createQuery("SELECT s FROM Site s").getResultList();
for (Site item : sites) {
if (item.getNom_Site().equals(Name_Site)) {
return item;
}
}
return null;
}
I get this error:
failed to lazily initialize a collection of role: xxx.xxx.xxx.xxx.xxx.site.Site.sequences, could not initialize proxy - no Session
When I use FetchType.EAGER, I get not only a Site object, but I also get all sequence objects, and all objects of other sequence associations. (I know it is the normal response.)
Could someone who knows why this attempt at lazy initialization doesn't work, please, tell me how to resolve this problem.
These lazy errors happens when the jpa tries to get the data after the session is closed.
But using eager will influence all the queries that include that entity.
Try to use a join fetch in the query instead of the eager.
Somewhere in your code you are calling Site.GetSequences(), maybe iterating in the view or in another part of your code. It doesn't look like the piece of code you gave are generating the exception.
I you try to use a collection that is not loaded to your entity, the code throws the exception you mentioned.
To solve this, identify where you are using the sequences and load them before you use by changing the fetch to EAGER or using the JOIN FETCH in your query.
Returning a hibernate managed entity (or a collection of hibernate managed entities) will most likely cause these sort of problems unless you are super cautious on what is being returned and what was populated by hibernate when session was available.
I would say create a DTO (or a collection of DTO) and populate its fields the way you like. There are many Entity to DTO conversion framework; my fav is ModelMapper.
I also tend to agree with other suggestions to play with FetchType but since DTOs are populated by us we know what we populated as opposed to entity-relationships which are populated by hibernate based on annotations.
If you need something in the DTO you simply ask the entity and since session would be available at that point of time you could populate any field that you think you would need on the UI.
I don't want to hijack this topic towards DTO and Entity but that's how I would do it.
This may be helpful too Avoid Jackson serialization on non fetched lazy objects
Error happen becouse you try execute getSequences(), but becouse of is lazy and session is alredy closed hibernate rais the error.
To avoid this error read read sequencese inside query method, "inside" session, like this:
public Site findSiteByName(String Name_Site) {
List sites = entityManager.createQuery("SELECT s FROM Sites").getResultList();
for (Site item : sites) {
if (item.getNom_Site().equals(Name_Site)) {
item.getSites();
return item;
}
}
return null;
}
This is a lazy loading, you read collenction just when you need it!
As stated by other SE members above, you are getting this error because session is already closed.
If you want to load a particular object then you can use Hibernate.initialize method. it will execute one additional query to fetch the data of related entity.
Therefore, it is as per need basis and will not be executed all times as compared to Eager loading
I'm working on a project that aims to solve common JPA problems when mapping entities to DTOs using ModelMapper. This issue has already been solved on the project. Project link: JPA Model Mapper
On this scenario I believe that we'd want to simply get null for all lazy load entities. For this question specifically, this could be done by using de JPA Model Mapper to map an entity to DTO.
I've already answered the same issue on this question: How to solve the LazyInitializationException when using JPA and Hibernate

hibernate collections with inheritance

My case is a form, with categories, questions, answers... A form has different categories, each of one have different questions and this questions one or more possible answers.
In my imnplementation of java, I hava an object called TreeObject that implements all relationship between elements (and other common properties as creation date...). This object has a list of childs and a parent to follow the hierarchy of the form. Then, Category, Form and other elements extends this class and add some extra functionality.
The database will be a table with all common data (tree object) and childs and parent relationship, and some other tables (forms, categories, ...) with specific data for each one. For this I use InheritanceType.JOINED
The code of the Tree Object class (UPDATED to include #kostja comments):
#Entity
#Table(name = "TREE_OBJECTS")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class TreeObject implements ITreeObject {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
#JoinTable(name = "CHILDRENS_RELATIONSHIP")
private List<TreeObject> children;
#ManyToOne(fetch = FetchType.EAGER)
private TreeObject parent;
//More parameters, getters and setters.
}
For example the Form class is:
#Entity
#Table(name = "FORMS")
public class Form extends TreeObject {
private String name;
//setters, getters and other stuff.
}
And the DAO has this method (I am using generics for simplifying the code but the code can be read):
public T makePersistent(T entity) {
setCreationInfo(entity);
setUpdateInfo(entity);
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
try {
session.saveOrUpdate(entity);
session.flush();
session.getTransaction().commit();
return entity;
} catch (RuntimeException e) {
session.getTransaction().rollback();
throw e;
}
}
Category, Questions and other elements are very similar to the Form class. The I skip them.
The problem is that the children list is not persisted correctly. For example the next test fails because getChildren().size() is 0 and not 1 (but other forms values are retrieved correctly, only the child list is empty):
#Test
public void storeFormWithCategory() throws NotValidChildException {
Form form = new Form();
form.setName("Test Form");
Category category = new Category();
form.addChild(category);
formDao.makePersistent(form);
Form retrievedForm = formDao.read(form.getId());
Assert.assertEquals(retrievedForm.getId(), form.getId());
Assert.assertEquals(retrievedForm.getChildren().size(), 1);
}
If I move the code of the child list into the Form class, it works correctly and the test is passed. But the list inside the parent class is not working. I cannot understand why, the only difference is the use of the inheritance.
The problem was solved removing the Interface. I have read that hibernate cannot work with intefaces, and this is the reason why children parameter is not implemented with interfaces. Removing the interface implementation and changing some setters and getters methods (as getChildren) to use TreeObject solve the issue. I have thinked that not using the interface with the DAO was enought to solve this issue. But seems that the getters and setters of the object also must not use the inteface.
Probably, when I have copied to Form object I haven't use the interface as a quick copy paste, and this is the reason why has worked correctly in this case.

self-reference field mapping in JPA

Lets say we have User entity class. User can be friends with other users. How can i map this self-reference collection field without creating a new entity called Connection or creating multiple entries in the database?
#Entity
public class User {
...
#ManyToMany
private Collection<User> friends;
...
}
USER_ID-FRIEND_ID
1 - 2
2 - 1 (duplicate... I don't need it)
Following is snapshot from my code for ElementEntity:
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<ElementEntity> children;
#JoinColumn(name = "ParentId", referencedColumnName = "ElementId")
#ManyToOne(fetch = FetchType.LAZY)
private ElementEntity parent;
Where on database there are fields:
ElementId - primary key;
ParentId relation with parent
You can't - you need both records in the database.
Actually, for friendship relations, I'd say that a graph database like neo4j is the proper thing to use. There you have the two users and simply add an edge "friends".
At least you will need a relational table.
So you have a USER table and a FRIENDS:
user_id friend_id
1 2
But #Bozho answer is way better than mine (neo4j).
Well, in fact you can.
You can use annotations like #PreUpdate, #PrePersists, #PostUpdate and so to convert manually the elements of a collection. This way your entity can render then them way you want while in database you only store a raw text.
A more pausible alternative will be to use #Convert annotation, available since jpa 2.1 (#UserType in hibernate). It tells jpa to convert the field into another type everytime it read/save in database.
For it you should use #Convert anotation, specifying and AttributeConverter object.
For example
public class Parent {
#Id
private Integer id;
#Convert(converter = FriendConverter.class)
private Set<Parent>friends;
}
And converter class like the following:
#Component
public class FriendConverter implements AttributeConverter<List, String>{
#Autowired
private SomeRepository someRepository;
#Override
public String convertToDatabaseColumn(List attribute) {
StringBuilder sb = new StringBuilder();
for (Object object : attribute) {
Parent parent = (parent) object;
sb.append(parent.getId()).append(".");
}
return sb.toString();
}
#Override
public List convertToEntityAttribute(String dbData) {
String[] split = dbData.split(".");
List<Parent>friends = new ArrayList<>();
for (String string : split) {
Parent parent = someRepository.findById(Integer.valueOf(string));
friends.add(accion);
}
return friends;
}
}
It is a dummy implementation but it gives you the idea.
As a personal comment, I do recommend to map the relationship as it should. In the future it will avoid you problems. AttributeConverter comes in handy when working with enums

Categories