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.
Related
I use following HQL query to delete a specific object in my database.
delete from com.ranking.Footballclub where id = 1
The problem I encounter when I do this, it breaks a foreign key.
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The DELETE
statement conflicted with the REFERENCE constraint
"FK_VKLC3OLNFZIT2FCYMMKDO2ERZ4". The conflict occurred in database
"sports", table "dbo.FOOTBALL_PLAYER", column 'CLUB_ID'
A sport teams has a slave of players. I could first delete all players before I delete the Footballclub. But a Footballclub has more than just players. It has for example a list of employees, transfers,... Their foreign keys will break as well.
For this case I'm looking for something in HQL to delete all slave fields in 1 statement.
you can set the ON DELETE CASCADE in the data base explicitly, or mark the required Child entity with the annotation #org.hibernate.annotations.OnDelete.
It will automatically adds the on delete to schema during the schema generation. like -
#OneToMany(fetch = FetchType.EAGER, mappedBy = "YOUR_PARENT",
cascade = CascadeType.REMOVE)
#org.hibernate.annotations.OnDelete(
action = #org.hibernate.annotations.OnDeleteAction.CASCADE)
private List<YOUR_CHILD> CHILDS;
You can only use #OneToMany(cascade=CascadeType.REMOVE) annotation at your Entity and delete it through entity manager. There is no option to do it with hql. Or use constraint in your DDL (foreign key delete cascade);
I'm trying to understand EclipseLink behaviour in case if I use native query. So I have Entity like this:
class Entity {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="other_entity_id")
private OtherEntity otherEntity;
#Column(name = "name")
private String name;
//gets ... sets ...
}
and corresponding table looks like:
**ENTITY**
INTEGER ID;
VARCHAR NAME;
OTHER_ENTITY_ID;
And then I run native query
Query query = getEntityManager().runNativeQuery("select * from ENTITY", Entity.class);
query.getResultList()
Within Entity I have declared OtherEntity otherEntity which is annotated with FetchType.LAZY, however my query selects (*) - all of the columns, including OTHER_ENTITY_ID. The question is - if I run native query that fetches all columns, will fields annotated with FetchType.LAZY populated as if they were FetchType.EAGER or not? I've never worked with EclipseLink before and tyring to decide is it worth using it or not so I would really appreciate any help
Thanks, Cheers
My first advice is to turn on EclipseLink's SQL logging, and execute the equivalent JPQL to load what you are looking for and see the SQL EclipseLink generates to accomplish that to get an understanding of what is required to build objects in your native queries based on your current mappings.
Relationships generally loaded with a secondary query using the values read in from the foreign keys, so eager or lazy fetching is not affected by the native query to read in "Entity" - the query requires the other_entity_id value regardless of the fetch type. When required based on eager/lazy loading, EclipseLink will issue the query required by the mapping.
You can change this though by marking that the relationship is to use joining. In this case, EclipseLink will expect not only the Entity values to be in the query, but the referenced OtherEntity values as well.
I have these classes:
#Entity
public class Invoice implements Serializable {
#Id
#Basic(optional = false)
private Integer number;
private BigDecimal value;
//Getters and setters
}
#Entity
public class InvoiceItem implements Serializable {
#EmbeddedId
protected InvoiceItemPK invoiceItemPk;
#ManyToOne
#JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
//Getters and setters
}
When i run this query:
session.createQuery("select i from InvoiceItem i").list();
It executes one query to select the records from InvoiceItem, and if I have 10000 invoice items, it generates 10000 additional queries to select the Invoice from each InvoiceItem.
I think it would be a lot better if all the records could be fetched in a single sql. Actually, I find it weird why it is not the default behavior.
So, how can I do it?
The problem here is not related to Hibernate but to JPA.
Prior to JPA 1.0, Hibernate 3 used lazy loading for all associations.
However, the JPA 1.0 specification uses FetchType.LAZY only for collection associations:
#OneToMany,
#ManyToMany
#ElementCollection)
The #ManyToOne and #OneToOne associations use FetchType.EAGER by default, and that's very bad from a performance perspective.
The behavior described here is called the [N+1 query issue][5], and it happens because Hibernate needs to make sure that the #ManyToOne association is initialized prior to returning the result to the user.
Now, if you are using direct fetching via entityManager.find, Hibernate can use a LEFT JOIN to initialize the FetchTYpe.EAGER associations.
However, when executing a query that does not explicitly use a JOIN FETCH clause, Hibernate will not use a JOIN to fetch the FetchTYpe.EAGER associations, as it cannot alter the query that you already specified how to be constructed. So, it can only use secondary queries.
The fix is simple. Just use FetchType.LAZY for all associations:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
More, you should use the Hypersistence Utils to assert the number of statements executed by JPA and Hibernate.
Try with
session.createQuery("select i from InvoiceItem i join fetch i.invoice inv").list();
It should get all the data in a single SQL query by using joins.
Yes there is setting you need: #BatchSize(size=25). Check it here:
20.1.5. Using batch fetching
small cite:
Using batch fetching, Hibernate can load several uninitialized proxies if one proxy is accessed. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways you can configure batch fetching: on the class level and the collection level.
Batch fetching for classes/entities is easier to understand. Consider the following example: at runtime you have 25 Cat instances loaded in a Session, and each Cat has a reference to its owner, a Person. The Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call getOwner() on each, Hibernate will, by default, execute 25 SELECT statements to retrieve the proxied owners. You can tune this behavior by specifying a batch-size in the mapping of Person:
<class name="Person" batch-size="10">...</class>
With this batch-size specified, Hibernate will now execute queries on demand when need to access the uninitialized proxy, as above, but the difference is that instead of querying the exactly proxy entity that being accessed, it will query more Person's owner at once, so, when accessing other person's owner, it may already been initialized by this batch fetch with only a few ( much less than 25) queries will be executed.
So, we can use that annotation on both:
collections/sets
classes/Entities
Check it also here:
#BatchSize but many round trip in #ManyToOne case
In this Method there are Multiple SQLs fired. This first one is fired for retrieving all the records in the Parent table. The remaining are fired for retrieving records for each Parent Record. The first query retrieves M records from database, in this case M Parent records. For each Parent a new query retrieves Child.
I ran into the below exception while mapping a one-to-one between 2 entities. The 1 first entity has embedded composite key. The second entity also has embedded composite key. The tables are part of legacy system. Data is flat, relations are not well defined. Please help.
Caused by: org.hibernate.AnnotationException: referencedColumnNames(FLAG_NAME) of net.javabeat.spring.model.ReferralsM.mnEditFlag referencing net.javabeat.spring.model.MnEditFlag not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:205)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:116)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1515)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1440)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1358)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1727)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1778)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:247)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:373)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:358)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
... 34 more
Here is my one to one mapping in the main/parent table.
#OneToOne(targetEntity = MnEditFlag.class, fetch = FetchType.LAZY)
#JoinColumn(name = "REFFLG", referencedColumnName = "FLAG_NAME", insertable = false, updatable = false)
MnEditFlag mnEditFlag;
The cause of the issue is that you are trying to use a single join column, while the identity of the referenced entity is defined by multiple columns. You simply have to define all the needed join colums and you are good to go:
#JoinColumns({
#JoinColumn(name = "REFFLG", referencedColumnName = "FLAG_NAME"),
#JoinColumn(name = "OTHER_KEY", referencedColumnName = "SOME_OTHER_NAME"))
...
})
MnEditFlag mnEditFlag;
OT: you should not need the targetEntity attribute on the OneToOne annotation. This is already defined by the type of the target entity: MnEditFlag. You probably need targetEntity only for untyped Collections.
EDIT: If there is a single join column, which is only part of the PK and you cannot change the existing tables, perhaps you can define a new join table with all necessary columns.
Then you define the join table to be used for the relationship:
#JoinTable(name="ReferralsM_MnEditFlag",
joinColumns={
#JoinColumn(name="REFERRALS_ID1", referencedColumnName="ID1"),
#JoinColumn(name="REFERRALS_ID2", referencedColumnName="ID2")
}, inverseJoinColumns={
#JoinColumn(name="REFFLG", referencedColumnName="FLAG_NAME"),
#JoinColumn(name="REFFLG2", referencedColumnName="FLAG_NAME2")
})
MnEditFlag mnEditFlag;
You would have to migrate the data to the new join table programmatically or by queries.
Unfortunately you cannot define a relationship with a partial PK with vanilla JPA, perhaps Hibernate has such a feature, like one-to-one by query, but I cannot confirm it.
EDIT2: The join table should contain all PK columns for both entities to be fully functional. That is why I have defined two join columns for each side in my example. The number of columns and their names are purely exemplary.
Extracting only the one join column you already have in your table would not add any value.
The optimal solution would be to change the entity tables so they define a proper relationship between the entities. It is sufficient to change only one of the tables and define it as the owning side as you did, but with all FK columns. This would require a migration effort, since you would need to add the data for the missing FK columns like described above.
EDIT3: The strategies I recommended were based on the assubmption that you want to have complete CRUD functionality. If you just want to pull the data for display or reporting, a view is perfectly fine. You can define the columns you need and map the whole view to a single entity. However, as it is a view, you will not be able to change the data or migrate it.
You could use targetEntity on #ManyToOne. The targetEntity should be equal to the parent (probably abstract) class.
The temporary solution is here depending to your class hierarchy:
https://hibernate.atlassian.net/browse/HHH-4975
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