Deleting JPA object fails due to foreign key constraints? - java

Why would the following query fail due to a foreign key constraint? There is no other way for me to delete the associated data that I am aware of.
Query query=em.createQuery("DELETE FROM Person");
query.executeUpdate();
em.getTransaction().commit();
The I believe the offending relationship causing the problem is the activationKey field:
2029 [main] ERROR org.hibernate.util.JDBCExceptionReporter - integrity
constraint violation: foreign key no action; FKCEC6E942485388AB
table: ACTIVATION_KEY
This is what I have now:
#Entity
#Table(name="person")
public class Person implements Comparable<Person> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id = 0;
#ElementCollection
#Column(name = "activation_key")
#CollectionTable(name = "activation_key")
private Set<String> activationKey = new HashSet<String>();
}

Why would the following query fail due to a foreign key constraint?
It looks like your bulk delete query is not deleting the entries from the collection table, hence the FK constraint violation.
And while the JPA spec explicitly writes that a bulk delete is not cascaded to related entities:
4.10 Bulk Update and Delete Operations
...
A delete operation only applies to
entities of the specified class and
its subclasses. It does not cascade to
related entities.
That's not exactly your case and I think that what you want to do should be supported.
You're probably facing one of the limitation of Hibernate's bulk delete, see for example:
HHH-3337 - Hibernate disregards #JoinTable when generating bulk UPDATE/DELETE for a self-joined entity
HHH-1917 - Bulk Delete on the owning side of a ManyToMany relation needs to delete corresponding rows from the JoinTable
I suggest raising an issue.
Workaround: use native queries to delete the collection table and then the entity table.

Related

Hibernate Cascade DELETE OneToMany does not work. Referential integrity constraint violation

I have a class Webtoon that contains a list of Episode. It is a one direction relation.
#OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#CollectionTable(name= "list_of_episodes")
List<Episode> listOfEpisodes = new ArrayList<>();
In my Unit test, I created a Webtoon object, then added an episode in the list listOfEpisodes.
When I try to delete the Episode using the Episode repository
this.episodeRepo.delete(episode);
I got the error :
violation de contrainte: "FK50GHKTDAXMN68TBU6KAYVUX9S:
PUBLIC.LIST_OF_EPISODES FOREIGN KEY(LIST_OF_EPISODES_ID) REFERENCES
PUBLIC.EPISODE(ID) (3)"
Referential integrity constraint violation: "FK50GHKTDAXMN68TBU6KAYVUX9S: PUBLIC.LIST_OF_EPISODES FOREIGN
KEY(LIST_OF_EPISODES_ID) REFERENCES PUBLIC.EPISODE(ID) (3)"; SQL
statement:
delete from episode where id=? [23503-200]
Why hibernate can't remove this object and update the list in Webtoon class ?
Try to change FetchType from EAGER to LAZY
Referential integrity is a property of data stating that all its references are valid. In the context of relational databases, it requires that if a value of one attribute (column) of a relation (table) references a value of another attribute (either in the same or a different relation), then the referenced value must exist.
From the error PUBLIC.LIST_OF_EPISODES FOREIGN KEY(LIST_OF_EPISODES_ID) REFERENCES PUBLIC.EPISODE(ID) you can clearly see that PUBLIC.EPISODE(ID) is referenced as a foreign key in PUBLIC.LIST_OF_EPISODES table so you cannot delete a parent unless you delete a child element.
Try using #JoinColumn instead of using #CollectionTable(name= "list_of_episodes")
To correct this issue, I first changed the uni-directional relation to bi-directional.
In Webtoon I have :
#OneToMany(cascade = CascadeType.ALL, mappedBy="webtoon", orphanRemoval = true)
#CollectionTable(name= "list_of_episodes")
List<Episode> listOfEpisodes = new ArrayList<>();
In Episode, I added a webtoon attribute
#ManyToOne(fetch= FetchType.LAZY)
#JoinColumn(name="webtoon_id")
Webtoon webtoon;
Because, it is lazy...I could not get the list as if I'm using eager, so I added a join select in my repository. And also when I delete, I have the cascade delete.

Soft delete on many-to-many association EclipseLink

I want to rewrite the call delete operation (on association table) on a many-to-many association sending by EclipseLink when we use only java code.
Let me explain the goal.
I have 3 tables, person, unit and an associative one : PerInUnit, so a person can be in multiple units and a units can contains many people. But I have some dependances on the PeInUnit table (If the person was present or not on a specific date, another table (Participations)), so I can't (and I don't want) delete a record. For that, I make softs deletes, so I can keep records to make some statistics.
I read already about the Customizer and AdditionalCriteria and I setted them to the PerInUnit class. It works perfectly => when I make an em.remove(myPerInUnit); the sql query sent to the db is Update PER_IN_UNIT SET STATUS='delete' WHERE id = #id; and the specified row as "delete" for status. Also, when I read all records, I don't have them with status "delete". But I use explicitly the PeeInUnit class.
Here is the code :
#Entity
#Table(name = "PER_IN_UNIT")
#AdditionalCriteria("this.status is null")
#Customizer(PIUCustomizer.class)
public class PerInUnit implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GEN_SEQ_PIU")
#SequenceGenerator(name = "GEN_SEQ_PIU", sequenceName = "SEQ_PIU", initialValue = 1, allocationSize = 1)
#Column(name = "ID")
private Long id;
#ManyToOne(cascade=javax.persistence.CascadeType.PERSIST)
#JoinColumn(name = "PER_ID")
private Person person;
#ManyToOne(cascade=javax.persistence.CascadeType.PERSIST)
#JoinColumn(name = "UNI_ID")
private Unit unit;
#Column(name = "STATUS")
private String status;
//Constructor, getters, setters
}
And the code for the PIUCustomizer :
public class PIUCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) {
descriptor.getQueryManager().setDeleteSQLString("UPDATE PER_IN_UNIT SET STATUS = 'delete' WHERE ID = #ID");
}
}
Here come the problem : As I use EclipseLink with bidirectionnal relationship I want to make some instruction like myUnit.getPeople.remove(currentPerson); (remove the current person from the unit "myUnit"). But EclipseLink sent the following instruction (during commit !) :
DELETE FROM PER_IN_UNIT WHERE ((UNI_ID = ?) AND (PER_ID = ?))
instead of the
Update PER_IN_UNIT SET STATUS='delete' WHERE ((UNI_ID = ?) AND (PER_ID = ?))
that I expected and raise (obviously, because of dependances (FKs)) the following exception :
Query: DataModifyQuery(sql="DELETE FROM PER_IN_UNIT WHERE ((UNI_ID = ?) AND (PER_ID = ?))")
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:157)
at test.Crud.update(Crud.java:116)
at test.Test.runTest(Test.java:96)
at test.Test.main(Test.java:106)
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-02292: integrity constraint (PEOPLE.FK_PAR_PIU) violated - child record found
Other problem (in the same kind), when I make something like System.out.prinln(myUnit.getPeople()) I have all the people in the unit "myUnit", including them having status 'delete'.
Is it possible to change some code/instructions/Customizer/etc in eclipseLink to change the delete call from person for PerInunit table, or I have to make my own queries and use them instead of using powerful of orm ?
Thanks for your answers and please forgive me for my poor english !
Fab
You should not be getting a delete when you call myUnit.getPeople.remove(currentPerson) unless you mapped Unit to Person with a ManyToMany using the PER_IN_UNIT table. Since you have an entity for the PER_IN_UNIT table, this would be wrong, as it really should be a Unit-> PerInUnit OneToMany mapping and then a PerInUnit -> Person ManyToOne mapping. The myUnit.getPeople.remove(currentPerson) call would then simply be getting the PerInUnit instance and marking its status as deleted, or dereferencing it and letting JPA call remove, thereby using your soft delete SQL query.
By using a ManyToMany mapping for the PER_IN_UNIT table, this mapping is completely independent to your PerInUnit entity mapping, and knows nothing about the entities that maybe cached or the soft deletes required to remove them. If you don't want to map the PER_IN_UNIT table as an entity, see http://www.eclipse.org/forums/index.php/t/243467/ which shows how to configure a ManyToMany mapping for soft deletes.

Entity mapping using a non primary key column

I am using hibernate and Mysql in the java project for persistence.
I have two entities Transaction and Service. Transaction is having many to one relation to service.
I wanted to use a non primary column(SERVICE_CODE) of type VARCHAR from Service table as a foreign key in the Transaction table. But when I do so I get the following exception.
SQL Error: 1452, SQLState: 23000
Cannot add or update a child row: a foreign key constraint fails.
SERVICE_CODE is defined as non null and unique in database.
Following example works fine if I use primary key from Service table for mapping.
#Entity
#Table(name="Transaction")
public class Transaction {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="TRANSACTION_ID")
long transactionId;
#ManyToOne
#JoinColumn(name="SERVICE_CODE")
Service service;
}
#Entity
#Table(name="SERVICE")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="SERVICE_ID")
Long serviceId;
#Column(name="SERVICE_CODE")
String serviceCode;
}
As explained in this article, you should use the referencedColumnName attribute of the #JoinColumn annotation to specify the referenced column of the foreign key relationship.
#ManyToOne
#JoinColumn(name="SERVICE_CODE", referencedColumnName="SERVICE_CODE")
Service service;
With this modification the DDL is generated correctly like this:
alter table Transaction
add constraint FK_5k37nrtsvi22y2jhsde903ps9
foreign key (SERVICE_CODE)
references SERVICE (SERVICE_CODE);
and with your original code like this (it references primary key of the SERVICE table instead of the SERVICE_CODE column):
alter table Transaction
add constraint FK_5k37nrtsvi22y2jhsde903ps9
foreign key (SERVICE_CODE)
references SERVICE;

Hibernate: Issue with OnDelete Annotation

Hello
I have the following two entities
#Entity
public class DocumentCollection {
#Id
#GeneratedValue
private Long id;
#OneToMany(targetEntity=Document.class,mappedBy="documentCollection",cascade=javax.persistence.CascadeType.ALL)
#OnDelete(action = OnDeleteAction.CASCADE)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Document> documents;
...
}
And:
#Entity
public class Document {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private DocumentCollection documentCollection;
...
}
When a DocumentCollection gets deleted, all referenced documents should be deleted too.
But I'm getting this error:
Cannot delete or update a parent row: a foreign key constraint fails (`tms_db`.`document`, CONSTRAINT `FK3737353BEB85533C` FOREIGN KEY (`documentCollection_id`) REFERENCES `documentcollection` (`id`))
I also tried putting the #OnDelete Annotation in the document class but didn't work.
So what am I missing? For your information, I'am using Hibernate 3.6.0.Final.
UPDATE: I did a mysql schema dump and noticed that there is no ON DELETE CASCADE statement in the document table schema. Hibernate only generates the foreign key constraints, which leads to the mentioned error. Has anybody an idea why hibernate does NOT generate the "ON DELETE CASCADE" statement?
Hope, somebody can help me
thx
#OnDelete annotation affects the way Hibernate generates database schema. If your schema is not generated by Hibernate, or haven't been updated after adding this annotation, it wouldn't work correctly.
If you want to use manually created database schema with #OnDelete(action = OnDeleteAction.CASCADE), you should manually define foreign key constraint in your schema as on delete cascade.

How to do bulk delete in JPA when using Element Collections?

I am having trouble working out how to do a bulk delete of a Person object using JPA, when the Person objects contain data stored using an #ElementCollection. Any ideas on how to do this would be much appreciated.
#Entity
#Table(name="at_person")
public class Person implements Comparable<Person> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id = 0;
#Column(name="name", nullable = true, length = 128)
private String name = "";
#ElementCollection
#Column(name = "email")
#CollectionTable(name = "person_email", joinColumns = #JoinColumn(name = "person_id"))
private Set<String> email = new HashSet<String>();
}
What I am doing at the moment is this, and it fails with a foreign key constraint error:
Query query=em.createQuery("DELETE FROM Person");
Caused by: java.sql.SQLException: integrity constraint violation:
foreign key no action; FKCEC6E942485388AB table: PERSON_EMAIL
If it can be a pure JPA annotation rather than a Hibernate annotation that would be a bonus!
I'll let you interpret the part of the JPA 2.0 specification that mentions that a bulk delete operation is not cascaded:
4.10 Bulk Update and Delete Operations
...
A delete operation only applies to
entities of the specified class and
its subclasses. It does not cascade to
related entities.
And the fact is that Hibernate won't cascade a delete to a collection table either. This has been reported in HHH-5529 and the suggested approaches are:
You could also (a) clean up the collection table yourself or (b) use cascading foreign keys in the schema.
In other words, (a) use native SQL or (b) use a cascade delete constraint at the database level - and you'll have to add it manually, I don't think you can use #OnDelete with the #ElementCollection annotation (same story as HHH-4301 IMO).

Categories

Resources