Hibernate unsaved instance exception using #ElementCollection - java

I am getting an exception
object references an unsaved transient instance - save the transient instance before flushing
thrown in the following code:
public void addThing(String key, String someData) {
Thing thing = new Thing();
booking.setData(someData);
booking.setParent(this);
bookings.put(key, thing);
}
The Parent mapping is:
#ElementCollection(fetch=FetchType.EAGER)
#Column(name="thing", nullable=false)
#MapKeyColumn(name="key")
#JoinColumn(name="parent_id")
protected Map<String, Thing> things = Maps.newHashMap();
The child ('Thing') mapping is:
#ManyToOne
private Parent parent;
According to the Hibernate manual:
There is no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent.
But - before I changed to the new #ElementCollection mapping so solve a problem where I was getting apparently phantom elements returned for a query, this code worked correctly.
I know I can save the element separately and then make a reference, but I prefer to have it done automatically, and I thought that was the way it is supposed to work. Any ideas?

#ElementCollection is not supposed to be used with collections of entities; it's used with collections of #Embeddable. If Thing is an entity, you don't use #ElementCollection, you use #OneToMany.
From the javadoc for #ElementCollection:
Defines a collection of instances of a basic type or embeddable class

Related

Spring Data JPA - How to persist nested object any existing or not

Using Springboot 2.4.0, I am trying to develop a simple module to manage a tipical relationship between the entities Product and Order (ManyToMany relationship).
In Order, I have this products field:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name="products_orders",
joinColumns=#JoinColumn(name="order_id"),
inverseJoinColumns=#JoinColumn(name="product_id")
)
private List<Product> products;
Since I don't need it, for now I am omitting an orders field in the Product entity.
Previous to this relationship, I have a CRUD for Product entity up and properly running, so I can insert products through a Product Repository:
public interface ProductRepository extends JpaRepository<Product, Integer>
I need to place an order with a number of products within it. I have this code:
List<Products> myListOfProducts = new ArrayList<>();
myListOfProducts.add(previouslyExistingProduct);
myListOfProducts.add(nonExistingProduct);
Order myorder = new Order(myListOfProducts, "mail", LocalDateTime.now());
myorder.save();
The question is: is it possible to get with the previous code that the new order gets saved as well as the products linked to it (saving both the existing and non existing products)?
This is the Order Repository:
public interface OrderRepository extends JpaRepository<Order, Integer>
This is what I got so far:
If I change CascadeType to CascadeType.MERGE, I get to link only existing products to the new order, but for new Products, I get this error:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.gallelloit.productcrud.model.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.gallelloit.productcrud.model.Product
Any other value I put to CascadeType (including CascadeType.ALL), I get the opposite: new products are saved and linked to the new order, but for existing products, I get this error:
detached entity passed to persist:
com.gallelloit.productcrud.model.Product; nested exception is
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.gallelloit.productcrud.model.Product
I know that I can do it "manually" getting each of the products, and saving them before the order.save(), but I was wondering if Spring Data did it for you.
Any idea?
Relying on CascadeType.MERGE is not safe in this context, because it updates the associated Products as well, meaning you may unintentionally overwrite existing Product data.
You can optimize the fetching of existing products by asking JPA to create proxies for the existing entities instead of actually fetching their associated state from the DB. This can be done using JpaRepository.getOne(). You'll need sth like:
order.setProducts(order.getProducts()
.map(product -> product.getId() == null ? productRepository.save(product) : productRepository.getOne(product.getId())
.collect(Collectors.toList());
If you decide on using CascadeType.PERSIST or CascadeType.ALL, you can of course replace productRepository.save(product) with just product. CascadeType.ALL doesn't make sense for #ManyToMany, though, since it implies CascadeType.REMOVE, which you most certainly don't want.
Also, I'm assuming the logic of saving the order happens inside a transactional context, right?

How do I make JPA merge insert new child objects for a OneToMany relationship

I'm having trouble getting my merge to cascade down when there are new child objects.
I have a parent object (City) with a set of child objects
#OneToMany(mappedBy = "parent", cascade = { CascadeType.ALL }, fetch=FetchType.LAZY )
public Set<Street> getStreets() {
return streets;
}
And a child object (Street) referencing the parent
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="City", nullable=false)
public City getParentCity(){
return parentCity;
}
And I'm running a daily routine to dump information into the database. All well and dandy until JPA runs into a new street. When it gives me an EntityNotFoundException. I'm assuming that it means there's no street already in the database with the new ID. From what I've read it's because the child is the actual owner of the transaction, but I need it to persist the new object if it runs into this "EntityNotFound" exception
I'm not using an auto-generated primary key, It's a key that already exists in the dataset. Currently I have access to a micro-service that uses em.merge() on the parent object.
I see that there's an option to delete objects that no longer exist (orphanRemoval); is there a way to add new ones?
Thanks
I dont know if it is the case, but with me, only works if I put the hibernate's annotations above the variable attributes themselves, not on getter methods.
What worked for me was 'manually' saving first the street, and then the City.

object references an unsaved transient instance: how to flush or return saved object

I use Spring 3.2.3 and Hibernate 4.2.3 and JDK 7.
I have a simple entity:
#Entity
public class Language {
#Id
#GeneratedValue
private long id;
#Column(nullable = false, length = 3, unique = true)
private String code;
}
I saved an instance of this entity using a #Service annotated class with a #Transactional annotated method which uses a DAO which saves the entity with
sessionFactory.getCurrentSession().save(object);
After that I used the saved Language entity for creating EntityX, which used it in an ManyToOne relation ...
lang=new Language();
// ...
languageService.saveLanguage(lang);
e=new EntityX();
// ...
e.setLanguage(lang);
otherService.saveEntity(e);
and EntityX is defined as ...
#Entity
public class EntityX {
#ManyToOne
#JoinColumn(nullable = false)
private Language language;
// ...
}
I always get the exception
Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: somepackage.Language
I try to use some cascade-definitions in EntityX's relation to Language as suggested in other posts, but it has no effect.
If I reload the saved Language entity by finding it by its code using some HQL-query, then everything works fine, but that it is far away from being 'nice'.
Unfortunately the save(...) method(s) of org.hibernate.Session does not return the saved object.
Has anybody any ideas how to solve it?
Is you code in a single #Transactional method?
If not the problem can be that, after any call to service method, transaction will be commit and session cleared. When you try to save entity, language object is not detected in session and managed as transient instance and give the error.
In case your code is under single transaction did you try a flush() before saving entity to force Hibernate store Language to database and assign it a valid #Identifier?
After all - IMHO - if you have a dependency from Entity and Language the best choice is:
#ManyToOne(cascade = CascadeType.ALL)
private Language language;
and change your code as:
e=new EntityX();
Language lang = new Language();
// ...
e.setLanguage(lang);
otherService.saveEntity(e);
and you don't need to persist entity in two steps (language + entity); manage language+entity as single item
PS: The save(...) method(s) of org.hibernate.Session does not return the saved object because the object will remain the same (reference doesn't change), just object properties changes (like the one marked this #Id, for example)!
EDIT:
Make an object persistent (session.save() it I mean) don't result in a immediate insert/update; without cascade hint Hibernate look doesn't detect dependency between EntityX and Language and doesn't perform a sql insert of Language before saving EntityX.
languageService.save(language) call doesn't perform session.flush() because you are under same #Transactional and without session.commit() no session.flush() is performed and best option is that Language object is still marked as transient.
You can do a check: extract services save code (language entityX) and put all in single #Transactional and check if Hibernate still give you error.
My best option is still perform a flush() in the middle or change your mapping, no other way
First, if the code field is the primary key (or you are at least using it as your primary ID for Hibernate), specify #Id:
#Id
#GeneratedValue(generator="assigned")
#Column(length = 3)
private String code;
Not specifying an ID column could be confusing Hibernate a bit; although don't quote me on that.
If save() still doesn't work after that, you can use merge():
Language lang = new Language("XYZ");
lang = session.merge(lange);

Efficiently determining the IDs of entities referenced via OneToMany relationship

Let's say I have a Hibernate entity that declares a OneToMany relationship to a different entity:
#Entity
public class SomeEntity {
#OneToMany(fetch = FetchType.LAZY)
private List<OtherEntity> otherEntities = new LinkedList<OtherEntity>();
[...]
}
When mapping SomeEntity to the corresponding DTO, all I need are the IDs that identify OtherEntity as primary key (i.e., I am not actually interested in OtherEntity instances).
Does Hibernate support this pattern, i.e., only retrieving the IDs of entities referenced via a OneToMany relationship?
I cannot influence how SomeEntity is retrieved (i.e., I have an existing SomeEntity instance retrieved within te scope of the current Hibernate session), but let's assume that lazy loading has not yet taken place, so just retrieving the child objects' IDs (rather than the complete objects) would actually yield a performance benefit.
Well, if you only need the entities' ids and you want to be economical about it, when you get those entities from the database you should state in your query that you only want to get the ids of each entry, using projections, something like :
SELECT Entity.id as entity FROM Entity WHERE ...
This will return an array of objects of the same type as Entity's id field type.
You can try obtaining the primary key without accessing the entity itself (without otherEntities.get(0).getId()). To do this you can use the PersistenceUnitUtil class:
PersistenceUnitUtil#getIdentifier(yourEntity)
The PersistenceUnitUtil can be obtained from the EntityManagerFactory. So it could be something like:
EntityManager em = ...
PersistenceUnitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
Unfortunately, I'm not aware if this will prevent the entity loading from occuring. However, just accessing the otherEntities collection or even obtaining references to each entity will not make the instance to be loaded; you need to invoke a method on the fetched entity in order to be sure it will be loaded.
You also might consider creating a #NamedQuery and return only the OtherEntity ID's.
HTH!
From hibernate reference, section 2.2.2.1.
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-property
Declare your columns as lazy initialized:
#Basic(fetch = FetchType.LAZY)
private String getYourProperty() {
}
You also need to disable proxies for your entity class and byte instrument it. There is an example here:
Making a OneToOne-relation lazy
You can use the below HQL as told in the documentation to establish this.
session.createQuery(select new OtherEntity(oe.id) OtherEntity oe
where oe.parentSomeEntity.someId = :someId).list();//also set someId.
Add a constructor in OtherEntity to set the id also there should be a mapping to SomeEntity in OtherEntity.
This HQL will give you a List<OtherEntity> with only id set in the bean.

Found shared references to a collection org.hibernate.HibernateException

I got this error message:
error: Found shared references to a collection: Person.relatedPersons
When I tried to execute addToRelatedPersons(anotherPerson):
person.addToRelatedPersons(anotherPerson);
anotherPerson.addToRelatedPersons(person);
anotherPerson.save();
person.save();
My domain:
Person {
static hasMany = [relatedPersons:Person];
}
any idea why this happens ?
Hibernate shows this error when you attempt to persist more than one entity instance sharing the same collection reference (i.e. the collection identity in contrast with collection equality).
Note that it means the same collection, not collection element - in other words relatedPersons on both person and anotherPerson must be the same. Perhaps you're resetting that collection after entities are loaded? Or you've initialized both references with the same collection instance?
I had the same problem. In my case, the issue was that someone used BeanUtils to copy the properties of one entity to another, so we ended up having two entities referencing the same collection.
Given that I spent some time investigating this issue, I would recommend the following checklist:
Look for scenarios like entity1.setCollection(entity2.getCollection()) and getCollection returns the internal reference to the collection (if getCollection() returns a new instance of the collection, then you don't need to worry).
Look if clone() has been implemented correctly.
Look for BeanUtils.copyProperties(entity1, entity2).
Explanation on practice. If you try to save your object, e.g.:
Set<Folder> folders = message.getFolders();
folders.remove(inputFolder);
folders.add(trashFolder);
message.setFiles(folders);
MESSAGESDAO.getMessageDAO().save(message);
you don't need to set updated object to a parent object:
message.setFiles(folders);
Simple save your parent object like:
Set<Folder> folders = message.getFolders();
folders.remove(inputFolder);
folders.add(trashFolder);
// Not set updated object here
MESSAGESDAO.getMessageDAO().save(message);
Reading online the cause of this error can be also an hibernate bug, as workaround that it seems to work, it is to put a:
session.clear()
You must to put the clear after getting data and before commit and close, see example:
//getting data
SrReq sr = (SrReq) crit.uniqueResult();
SrSalesDetailDTO dt=SrSalesDetailMapper.INSTANCE.map(sr);
//CLEAR
session.clear();
//close session
session.getTransaction().commit();
session.close();
return dt;
I use this solution for select to database, for update or insert i don't know if this solution can work or can cause problems.
My problem is equal at 100% of this: http://www.progtown.com/topic128073-hibernate-many-to-many-on-two-tables.html
I have experienced a great example of reproducing such a problem.
Maybe my experience will help someone one day.
Short version
Check that your #Embedded Id of container has no possible collisions.
Long version
When Hibernate instantiates collection wrapper, it searches for already instantiated collection by CollectionKey in internal Map.
For Entity with #Embedded id, CollectionKey wraps EmbeddedComponentType and uses #Embedded Id properties for equality checks and hashCode calculation.
So if you have two entities with equal #Embedded Ids, Hibernate will instantiate and put new collection by the first key and will find same collection for the second key.
So two entities with same #Embedded Id will be populated with same collection.
Example
Suppose you have Account entity which has lazy set of loans.
And Account has #Embedded Id consists of several parts(columns).
#Entity
#Table(schema = "SOME", name = "ACCOUNT")
public class Account {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "account")
private Set<Loan> loans;
#Embedded
private AccountId accountId;
...
}
#Embeddable
public class AccountId {
#Column(name = "X")
private Long x;
#Column(name = "BRANCH")
private String branchId;
#Column(name = "Z")
private String z;
...
}
Then suppose that Account has additional property mapped by #Embedded Id but has relation to other entity Branch.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "BRANCH")
#MapsId("accountId.branchId")
#NotFound(action = NotFoundAction.IGNORE)//Look at this!
private Branch branch;
It could happen that you have no FK for Account to Brunch relation id DB so Account.BRANCH column can have any value not presented in Branch table.
According to #NotFound(action = NotFoundAction.IGNORE) if value is not present in related table, Hibernate will load null value for the property.
If X and Y columns of two Accounts are same(which is fine), but BRANCH is different and not presented in Branch table, hibernate will load null for both and Embedded Ids will be equal.
So two CollectionKey objects will be equal and will have same hashCode for different Accounts.
result = {CollectionKey#34809} "CollectionKey[Account.loans#Account#43deab74]"
role = "Account.loans"
key = {Account#26451}
keyType = {EmbeddedComponentType#21355}
factory = {SessionFactoryImpl#21356}
hashCode = 1187125168
entityMode = {EntityMode#17415} "pojo"
result = {CollectionKey#35653} "CollectionKey[Account.loans#Account#33470aa]"
role = "Account.loans"
key = {Account#35225}
keyType = {EmbeddedComponentType#21355}
factory = {SessionFactoryImpl#21356}
hashCode = 1187125168
entityMode = {EntityMode#17415} "pojo"
Because of this, Hibernate will load same PesistentSet for two entities.
In my case, I was copying and pasting code from my other classes, so I did not notice that the getter code was bad written:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "credito")
public Set getConceptoses() {
return this.letrases;
}
public void setConceptoses(Set conceptoses) {
this.conceptoses = conceptoses;
}
All references conceptoses but if you look at the get says letrases
I too got the same issue, someone used BeanUtils.copyProperties(source, target). Here both source and target, are using the same collection as attribute.
So i just used the deep copy as below..
How to Clone Collection in Java - Deep copy of ArrayList and HashSet
Consider an entity:
public class Foo{
private<user> user;
/* with getters and setters */
}
And consider an Business Logic class:
class Foo1{
List<User> user = new ArrayList<>();
user = foo.getUser();
}
Here the user and foo.getUser() share the same reference. But saving the two references creates a conflict.
The proper usage should be:
class Foo1 {
List<User> user = new ArrayList<>();
user.addAll(foo.getUser);
}
This avoids the conflict.
I faced similar exception in my application. After looking into the stacktrace it was clear that exception was thrown within a FlushEntityEventListener class.
In Hibernate 4.3.7 the MSLocalSessionFactory bean no longer supports the eventListeners property. Hence, one has to explicitly fetch the service registry from individual Hibernate session beans and then set the required custom event listeners.
In the process of adding custom event listeners we need to make sure the corresponding default event listeners are removed from the respective Hibernate session.
If the default event listener is not removed then the case arises of two event listeners registered against same event. In this case while iterating over these listeners, against first listeners any collections in the session will be flagged as reached and while processing the same collection against second listener would throw this Hibernate exception.
So, make sure that when registering custom listeners corresponding default listeners are removed from registry.
My problem was that I had setup an #ManyToOne relationship. Maybe if the answers above don't fix your problem you might want to check the relationship that was mentioned in the error message.
Posting here because it's taken me over 2 weeks to get to the bottom of this, and I still haven't fully resolved it.
There is a chance, that you're also just running into this bug which has been around since 2017 and hasn't been addressed.
I honestly have no clue how to get around this bug. I'm posting here for my sanity and hopefully to shave a couple weeks of your googling. I'd love any input anyone may have, but my particular "answer" to this problem was not listed in any of the above answers.
I had to replace the following collection initilization:
challenge.setGoals(memberChallenge.getGoals());
with
challenge.setGoals(memberChallenge.getGoals()
.stream()
.map(dmo -> {
final ChallengeGoal goal = new ChallengeGoalImpl();
goal.setMemberChallenge(challenge);
goal.setGoalDate(dmo.getGoalDate());
goal.setGoalValue(dmo.getGoalValue());
return goal;
})
.collect(Collectors.toList()));
I changed
#OneToMany( cascade= CascadeType.ALL)
#JoinColumn(
name = "some_id",
referencedColumnName = "some_id"
)
to
#OneToMany(mappedBy = "some_id", cascade= CascadeType.ALL)
You're using pointers(indirectly), so sometimes you're copying the memory address instead of the object/collection you want. Hibernate checks this and throw that error. Here's what can you do:
Don't copy the object/collection;
Initiate a new empty one;
Make a function to copy it's content and call it;
For example:
public Entity copyEntity(Entity e){
Entity copy = new Entity();
e.copy(name);
e.setCollection2(null);
e.setCollection3(copyCollection(e.getCollection3());
return copy;
}
In a one to many and many to one relationship this error will occur. If you attempt to devote same instance from many to one entity to more than one instance from one to many entity.
For example, each person can have many books but each of these books can be owned by only one person if you consider more than one owner for a book this issue is raised.

Categories

Resources