I have an entity that has a collection in it. The collection is a OneToMany unidirectional relationship storing who viewed a particular file. The problem I am having is that after I load the entity and try to update the collection, I don't get any errors, but the collection is never updated:
Entity:
#OneToMany(cascade = CascadeType.PERSIST)
#JoinTable
protected List<User> users;
File Servlet
#In
private EntityQuery<File> File_findById;
...
File file = File_findById(fileId);
file.getUsers().add(user);
session.update(file);
Even though I call session.update(file) and I see stuff in hibernate logs, I don't see anything in the database indicating that it was saved.
Walter
Use property access instead of field access because of it enables Automatic dirty checking by Hibernate and use #Cascade(CascadeType.SAVE_UPDATE) instead
#OneToMany
#JoinTable
#Cascade(CascadeType.SAVE_UPDATE)
public List<User> getUsers() {
return this.users;
}
regards,
Why do you cascade the persist (create) operation only if you also want to cascade merge / update?
With EJB3 / JPA annotations, you may want to add another cascading type:
CascadeType.MERGE
Or maybe even:
CascadeType.ALL (which also covers Hibernate specific operations like save-update, lock)
If you want to use Hibernate extensions, you may want to use the #Cascade annotation to cascade:
SAVE_UPDATE
Related
Migrated jpa 1.x to jpa 2.x
User Entity
Entity
public class User {
#OneToMany(fetch= fetch type.Eager,
cascade = CascadeType.ALL)
Private List<Address> userAddresses =
new ArrayList<>();
}
#Entity
public class Addresses {
#ManyToOne(fetch=fetch type.EAGER)
#JoinColumn(name = "address_type_id")
private AddressType addressType;
}
When I tried to save an address,
Getting below error
However, this field does not allow cascade persist. You cannot flush unmanaged objects or graphs that have persistent associations to unmanaged objects.
Suggested actions:
a) Set the cascade attribute for this field to CascadeType.PERSIST or CascadeType.ALL (JPA annotations) or "persist" or "all" (JPA orm.xml),
b) enable cascade-persist globally,
c) manually persist the related field value prior to flushing.
d) if the reference belongs to another context, allow reference to it by setting StoreContext.setAllowReferenceToSiblingContext().
For the addresstype getting error.
This error will gone when I add cascadetype.persist
Entity
public class Addresses {
#ManyToOne(fetch=fetchtype.EAGER,
cascade= cascadetype.PERSIST)
#JoinColumn(name ="address_type_id")
private AddressType addressType;
}
But I don't want to save to addresstype. So any other solution to fix this issue?
From https://openjpa.apache.org/builds/2.4.2/apache-openjpa/docs/jpa_2.2.html
Cascade persist behavior
In previous releases, OpenJPA would check the database for the existence of the related Entity before persisting the relationship to that Entity. This resulted in an extra Select being sent to the database. In 2.2.0, code was added so that when cascading a persist to a related Entity without persistence state, the persist (insert) will happen without first checking the database. This may result in an EntityExistsException if the related Entity already exists in the database. To revert this behavior to the previous release, set the value of the openjpa.Compatibility property CheckDatabaseForCascadePersistToDetachedEntity to true.
After set CheckDatabaseForCascadePersistToDetachedEntity true, the error gone.
Entity{
String code;
String parentCode;
...
#ManyToOne
#JoinColumn(name="parentCode",referencedColumnName="code")
Entity parentEntity;
}
My entity class is like this. what i want to do is using findAll() to get an entity list with each entity get its own direct parent. But spring jpa will get parent's parent until the root , i need to avoid this.
Thank you!
Since a default fetch type for a #ManyToOne relation is an FetchType.EAGER I think you have just add a fetch type as LAZY explicitly:
Entity{
String code;
String parentCode;
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="parentCode",referencedColumnName="code")
Entity parentEntity;
}
It's not about Spring JPA but JPA itself. When you add a relationship, following defaults apply unless specified otherwise.
#xxToOne - FetchType.EAGER
#xxToMany- FetchType.LAZY
So, in your example you have a #ManyToOne which has a default EAGER fetch and one join query is appended. If your parent has another #xxToOne it adds one more join and so on. It's good to know the boundaries of your entities and decide which type of FetchType is required.
Even if you add like this:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="parentCode",referencedColumnName="code")
Entity parentEntity;
.. if you parent has more relationships you might be getting everything loaded while fetching parent. Thus, all relationships need to be Lazy. It's a design choice based on entities.
But be aware about the ORM's N+1 problem : JPA Hibernate n+1 issue (Lazy & Eager Diff)
I've got this structure of project:
class UserServiceSettingsImpl {
...
#ManyToOne
private UserImpl user;
#ManyToOne
private ServiceImpl service;
...
}
class ServiceImpl {
....
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "service", orphanRemoval = true)
private Set<UserServiceSettingsImpl> userServiceSettings;
....
}
class UserImpl {
....
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
private Set<UserServiceSettingsImpl> serviceSettings;
....
}
I am trying to delete Service and everything that belongs to it (UserServiceSettingsImpl), but accidentally, this settings are not being removed (I suppose because they are not orphans since UserImpl has them too). So the thing is: is there a way to delete Settings, without deleting them from user manually (there could be a lot of users with a lot of settings, iterating through it could take a lot of time) ?
You are correct in why the UserServiceSettings are not being deleted when deleting a service if they are also referenced by a User. They are not orphans and will have to be deleted explicitly per your business logic.
Three ideas:
Use the ORM to batch delete entities.
It's not much different than iterating, but might be optimized while still using the ORM.
List settingsCopy = new ArrayList<>(service.getSettings());
service.getSettings().clear();
myDao.deleteAll(settingsCopy);
Use direct HSQL/SQL to batch delete.
This depends on what framework you are using, but generally would be something like this, probably in your repository/dao class:delete from UserServiceSettingsImpl o where o.service.id = ? However, hibernate does not support JOINs when deleting, afaik, so this doesn't work as written. It's generally necessary to rework the HSQL to use a "delete where id IN(...)" type format.
Setup CASCADE DELETEs and CASCADE UPDATEs in your database DDL, outside of the ORM framework. (Not recommended.)
However, the last two options have problems if there is chance that service's and user's UserServiceSettings can be modified at same time via multiple threads (even with correct transaction boundaries), or if those entities will be used within the orm context after the delete without a reload. In that case, you will likely run in to unexpected and sporadic errors with the last two approaches, and instead, should iterate the settings and delete via the ORM, even if it is inefficient.
Even with the first approach, it can be tricky to avoid errors in highly concurrent environments when deleting shared entities.
You're correct that you cannot delete them in any kind of automatic way - they will never be orphans. I think the best you can do is just write yourself a helper method. e.g. if you have a ServiceDao class, you would just add a helper as:
public void deleteServiceAndSettings(Service service) {
for (UserServiceSettings setting : service.getUserServiceSettings()) {
session.delete(setting);
}
session.delete(service);
}
I have an entity which contains:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "assessment")
#OrderBy(value = "order ASC")
private List<AssessmentPart> assessmentParts = new LinkedList<>();
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "assessment")
private List<AssessmentText> texts = new LinkedList<>();
as you see there are two collections which needs to be eagerly loaded. This is not working and hibernate throws an exception:
Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
That's because Hibernate can't fetch multiple collections with one go. But if I change the List to Set and the LinkedList to HashSet this part works fine but the other - more annoying issue occurs.
When I am trying to get the entity from the database using:
entityManager.find(entityClass, primaryKey);
It fails with:
org.hibernate.AssertionFailure: null identifier
I am sure that ID I am passing to find method is not null, I've debugged and I am sure of this. It somehow disappears inside Hibernate.
If I change collection types to LAZY everything just works without errors but there are some circumstances where I need to use EAGER.
Does anybody have a solution how to fix it? Either I could have a set but prevent assertion error from occurring or I could have a list but somehow avoid multiple fetch bags error.
I am using:
Hibernate 4.2.2.Final
Tomcat 7
JPA 2.0
JDK 1.7
EDIT
I've just discovered that adding #Fetch(FetchMode.SELECT) fixes the issue and I can use multiple list with EAGER type, but is there anyway to solve this by not using Hibernate specific annotations? And why it fixed the issue in the first place?
The root cause of the problem is that when Hibernate fetches SQL query results there is no simple way to tell which child element belongs to which collection. See this blog entry for more detailed explanation with an example. To summarize you have following workarounds:
Load each collection separately using subselect #Fetch(FetchMode.SELECT)
Force usage of list instead of bag by adding index column #IndexColumn(name="LIST_INDEX")
Use unordered collection like Set.
If you are using Hibernate and you do not care using Hibernate annotations :
Annotate your collection fields with:
#LazyCollection(LazyCollectionOption.FALSE)
Remember to remove the fetchType attribute from the #OneToMany annotation.
I have a common User / Role setup, with a user_role join table. I'm trying to use Spring's HibernateTemplate to mass delete all locked users like this:
getHibernateTemplate().bulkUpdate("delete from User where locked=?", true);
If the user being deleted does not have any roles (no record in the user_role table), then everything goes fine; however if the user does have a role record, I'm getting the following error:
integrity constraint violated - child
record found
Roles are defined in User.java like this:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = { #JoinColumn(name = "user_id") }, inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<Role>();
So how can I batch delete users even if a user has child records? Thanks!
Bulk delete operations are not cascaded to related entities as per the JPA specification:
4.10 Bulk Update and Delete Operations
Bulk update and delete operations
apply to entities of a single entity
class (together with its subclasses,
if any). Only one entity abstract
schema type may be specified in the
FROM or UPDATE clause.
...
A delete operation only applies to
entities of the specified class and
its subclasses. It does not cascade
to related entities.
However, I'd expect the JPA provider to deal with join tables. Sadly, Hibernate doesn't and this is logged in HHH-1917. I'm afraid you'll have to fall back on native SQL to clean up the join table yourself or to use cascading foreign keys in the schema.
Application-level cascading (cascading through hibernate annotations or JPA annotations) only work if the actual entity is actually loaded from the db. When you use the hibernate template with HQL, you'll notice that the entities are not loaded, and the HQL is directly converted to SQL to be executed.
If you want to batch delete you have to use an HQL query to delete all relevant tables (ie roles) before deleting the parent table data.
I'm not entirely sure because it's hard for me to recreate this problem, but I think you might need to add a cascade to your #ManyToMany
#ManyToMany(cascade = CascadeType.ALL)
Since you want to bulk delete something that have a ManyToMany related items, you first have to delete the relation (in the join table), or do a loop and for each item, delete manually (insane and too much heavy).
So, since JPQL does not allow to do it, a possible way is to make a native SQL query for deleting the id you want in the related table, and then, do the bulk delete.