I have the following entities:
#Entity
public class B{
#OneToMany
private List<C> cList;
private Long d;
}
In my managed bean, I need to load a specific b (which is perfectly working) in order to edit the contained attributes (cList, d):
#ManagedBean
public class Bean{
private B b;
public void onEvent(Long bId){
b = bManager.load(bId);
}
}
The attributes of B will have to be edited using a JSF-Form. I do not want these changes to be reflected to the database.
The problem is pretty much the same like in this (old) thread.
But none of the suggestions worked in my case (tried em.flush(), em.detach(), session.evict()).
Is there no solution except deep copying?
It is possible to do it, but first would be better to know what hibernate is doing and why you are getting exception. Here is documentation about object states
If you want to access list with objects C or you want to modify some of them, you must fetch it before it gets to your managed bean. By default hibernate is fetching objects lazy and associated objects will be loaded when you access them, but preconditions is to have a transaction and session attached to objects. So in your managed bean objects are detached and list of C cannot be fetch at that time. To solve that problem you must fetch all object that you want to change before they gets to the managed bean. i.e.
#OneToMany
#Fetch(FetchMode.JOIN) // load it with sql join
private List<C> cList;
There many other ways you can achieve same result. So now you can update your B and list of C
entities and then call update function for your B entity.
Hope it helps.
Related
I have the following scenario and could not find any solution for this so far.
Imagine the following Hibernate model consisting of 3 different types with one-to-many relations:
public class A {
#Transient
private String someRuntimeData;
#OneToMany
private Set<B> collA;
#OneToMany
private Set<C> collB;
}
public class B {
#ManyToOne
private A parent;
}
public class C {
#ManyToOne
private A parent;
}
Imagine that the database contains many B'c and C's that may or may not have a parent relation to A yet. I need to create an in-memory cache that contains all B's and all C's, and unfortunately there is a lot of transient data in different places involved, which requires me to suppress caching multiple instances of the same parent object A.
class SomeClass {
#Transactional
protected void init() {
bList = repoB.readAll();
cList = repoC.readAll();
}
}
The problem is that I don't know how or if it is even possible to tell JPA/Hibernate to retain and reuse an object instance (with its identity) of previously loaded entities in the following way:
Load the full collection of B's with their optional parents of A, then load the full collection of C's, where any transitively loaded instance of A (through B) is reused. Where appropriate, both B and C instances then point to the same in-memory object.
I would be very thankful if anyone could explain on how to realize this with out-of-box features of JPA/Hibernate before I swallow the bitter pill and and remap everything by hand.
Thank you in advance!
It was not obvious from the provided code snippets what the problem could have been in this scenario.
For full disclosure, the init() method was called via self-invocation from the classes constructor:
#Component
class SomeClass {
public SomeClass() {
init();
}
#Transactional
protected void init() {
bList = repoB.readAll();
cList = repoC.readAll();
}
}
Since I did not configure any aspect-weaving for load- or runtime, the compiler created a default Spring Proxy object for SomeClass. Therefore any proxy logic is fully circumvented when self-invocation is in play.
In result, the only transactions opened where the default, dedicated ones for each read operations, and the shared parent objects where redundantly loaded.
I'm using Spring Boot and Hibernate.
Lets assume we have two entities:
#Entity
public class A{
#OneToMany(mappedBy = "objectA",fetch = FetchType.EAGER,cascade = CascadeType.ALL,orphanRemoval=true)
private Set<B> objectSet = new HashSet<>();
}
#Entity
public class B{
#ManyToOne
private A objectA;
}
And we have two transacional methods;
deleteB_X(int idB){
entityManager.remove(entityManager.find(idB,B.class));
}
deleteB_Y(int idB){
B obj=entityManager.find(idB,B.class);
obj.getObjectA().getObjectSet().remove(obj);
}
What I understand (correct me if I'm wrong):
We have orphanRemoval=true so deleteB_Y(int) will work.
By setting mappedBy argument we say that class A is "the owning
site" of relation.
CascadeType is used when we persist/update/merge/remove class A (then it invokes persist/update/merge/remove on child property objectSet). I think we can say that it protects me from situation where I end up with object of B and no object of A class (unless we manually add some B objs).
From what I understand CascadeType should not interfare with orphanRemoval, because CascadeType takes care of things where we do 'some stuff' with A's objects (and then recursively do it to B's objects). And here is something that I don't understand at all.
Why deleteB_x(int) doesn't work and why if we remove CascadeType it starts working? I feel like that deleteB_X(int) is much cleaner solution to removing object B from the DB than deleteB_Y(int), but sadly it won't work since it colides with CascadeType.
EDIT1.
Method deleteB_X(int) just doesn't remove object from DB, if we remove cascade = CascadeType.ALL evertyhing works just fine. Why?
The issue was that my class A was fetched EAGER in class B instance and because of that (I assume) that there was a conflict when I was deleting B's instance alone without taking care of the same B instance in private Set<B> objectSet. Changing EAGER to LAZY or excluding CascadeType.PERSIST from #OneToMany(cascade=...) solved my issue.
I'm new to JPA Hibernate implementation and I'm trying to understand how EntityManager.merge works.
I have the following scenario of two classes with #ManyToOne relation e.g
class A{
private String name;
........
get...
set...
}
class B{
private String name;
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.MERGE)
private A a;
........
get...
set...
}
And I have GUI where you can view and change only B property's - the A class is not loaded and there is no Open Session in View.
When trying to save(merge) changes in back-end:
entityManager.merge(b); //the object "b" created from view has "a" set to null
In database in table B the foreign key relationship(referencing to table A) is lost - set to null;
This is not what a want I want to keep the relationship and merge-save the changes made in the GUI to object B with out losing the Many-To-One relationship
Maybe what I'm expecting is not possible and what I need to do is:
dbB b = entityManager.find(B.class, id);
dbB.setName(b.getName());
dbB.set........
entityManager.merge(dbB);
If in entityManager.merge(b); the relation b.a is null, merge will delete the foreign key. That's because the value null doesn't mean the reference should be ignored but that the reference should be removed (i.e. there is no reference anymore).
I want to keep the relationship and merge-save the changes made in the GUI to object B with out losing the Many-To-One relationship
One option would be to read the entity from the database with a being initialized to a lazy proxy and pass it to the gui. During that process the entity will most likely get detached and hence you need to merge it during save. However, since a won't be null in that case, the reference should not get lost.
I am a bit confused about managing relationship in JPA.
basically I have two entities with a One to Many relationship
A configuration can have have a one or many email list associated with it.
#Entity
public class Config {
#OneToMany(mappedBy="owner",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<Email> emailReceivers;
}
#Entity
public class Email {
#ManyToOne
private Config owner;
}
In an EJB and during update/merge operation wherein I would edit the list of emails associated with a configuration,
I thought that I dont need to explicitly call the delete operation on my email Entity and I would just manage the relationship by deleting the email in my configuration email list.
#Stateless
public class ConfigFacadeImpl implements ConfigFacade{
#EJB
private ConfigDao configDao;
#EJB
private EmailDao emailDao;
#Override
public void update(Config Config, List<Email> emailsForDelete) {
if(emailsForDelete!=null && emailsForDelete.size() > 0){
for(Email emailTemp: emailsForDelete){
Email email = emailDao.find(emailTemp.getId());
emailDao.delete(email); // Do I need to explicitly call the remove??
config.getEmailReceivers().remove(email);
}
}
configDao.update(config);
}
}
If I don't execute the delete and only remove it from the list, it wont erase my table row.
The UI and the database is now not in sync as the UI would not show the email(s) that I have deleted but when you check the database, the row(s) are still there.
Is it required? I thought JPA would handle this for me if I would just remove it in my entities.
UPDATE
I have tweaked my code to get the entity from the database first before making any changes but still it is not deleting my child email entities. I wonder if this is an apache derby issues. (This is the correct way right as I am passing my entities from my JSF managed bean into my EJB so I need to get the sync from the DB first.)
#Override
public void update(Config config, List<Email> emailsForDelete) {
Config configTemp = configDao.find(config.getId());
if(emailsForDelete!=null && emailsForDelete.size() > 0){
for(Email emailTemp: emailsForDelete){
configTemp.getEmailReceivers().remove(emailTemp);
}
}
configDao.update(config);
}
Since you have already defined cascade type = CascadeType.ALL, JPA should take care of the deletion. Explicit Delete statement is not required.
These two statements are not required:
Email email = emailDao.find(emailTemp.getId());
emailDao.delete(email); // Do I need to explicitly call the remove??
Instead, you may want to just find the matching emailReceiver in config.getEmailReceivers() and remove the matching EmailReceivers as you are doing. There is no need to load the Email entity from the database.
EDIT: To delete orphan objects, you may want to include CascadeType.DELETE_ORPHAN cascade attribute along with CascadeType.ALL.
This is the same issue as in Why merging is not cascaded on a one to many relationship
Basically, JPA can only cascade over entities in your collection. So changes to child objects removed from the collection are never putinto the context, and so can't be pushed to the database. In this case, the oneToMany is controlled by the manytones back pointer, so even collection changes won't show up unless the child is also merged. Once a child is pruned from the tree, it needs to be managed and merged individually for changes to it to be picked up.
With JPA 2.0, you can use the option orphanRemoval=true in parent entity
Example:
#Entity
public class Parent {
...
#OneToMany(mappedBy="parentId",cascade=CascadeType.ALL, orphanRemoval=true)
private List<Child> childList;
...
}
I have code like:
#Entity
#Table(name = "A")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class A
{
#OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.EAGER, mappedBy="a")
public B getB() {};
}
#Entity
#Table(name = "B")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class B
{
#OneToOne(cascade={}, fetch=FetchType.LAZY)
#JoinColumn(name="A_ID")
public A getA() {};
}
each time when A is loaded there is query for B. Why is A.getB() not cached after A is loaded and is it possible to cache it?
Workaround that work for me is create additional method with #OneToMany
#OneToMany(cascade={}, fetch=FetchType.EAGER, mappedBy="a")
public Set<B> getBSet() {};
#Transient
public B getB() { return b.iterator().next(); }
I'm not very happy with this solutions, but it works and I can't find other way.
Try putting #Cache annotation on getB() getter as well. My observations are that if you cache the object, it's associations may not be considered cached.
It may be a little more work, but you could try making the fetchType Lazy, and
do the fetching of B explicitly. That way you could check whether the instance of B has already been loaded or not?
On a side note, have you seen this post? I think the problem is similar:
https://forum.hibernate.org/viewtopic.php?p=2378461
I feel that the original answer does not cover entirly why this is happening.
Why OneToOne is not cached ?
It is not cached because class A is not the owner of the relationship and does not contain the #JoinColumn inside its table. Therefore there is no way for class A to tell what is the ID of class B. This is why when trying to retrieve class A it needs to send a query for class B to figure out what the ID of class B is, but when it sends the query the class B is already loaded so there is no need for it to actualy retrieve it from the cache.
When OneToOne will be cached ?
Now if you navigate the opposite way from class B to class A then you will hit the cache straight away :)
Why is #OneToMany(cascade={}, fetch=FetchType.EAGER, mappedBy="a") working ?
In hibernate collections are cached in their dedicated region known as collection cache. Hibernate caches the primary keys of the entities that make up the collection. Not the entities themselves; i.e. there is no Set stored somewhere in the second level cache.
Once the primary key for is retrieved from the collection cache region it falls back to the regular entity cache to retrieve the actual object. Therefore the #OneToMany hack works for you.