Foreign key constraint fails while using Spring Data - mysql

I'm using Spring Data to access my MySQL database with following entities
#Entity
#Table(name = "tbl_product")
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn
private Category category;
}
#Entity
#Table(name = "tbl_category")
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
}
Now I try to use my Controller to delete Product
#RequestMapping(path="/remove/{id}", method=RequestMethod.DELETE)
#ResponseBody
public String removeProduct(#PathVariable Long id) {
return productService.removeProduct(id);
}
public String removeProduct(Long productID) {
try {
productRepository.delete(productID);
return "OK";
} catch (Exception e) {
logger.info(e.getMessage());
return "Error";
}
}
Now, for example if there are 3 Products with Category A and 1 with Category B, I can remove the one with Category B. But if I try to remove any other one, I got
Cannot delete or update a parent row: a foreign key constraint fails
(product_db.tbl_product, CONSTRAINT FKfq7110lh85cseoy13cgni7pet
FOREIGN KEY (category_id) REFERENCES tbl_category (id))

It is because of the cascading that you are applying by cascade = CascadeType.ALL.
So, whenever you delete any product, JPA deletes the category id too. It works well if that category is not associated with any other product(which is what is happening in your second case). But if the category is associated with any other product then the foreign key constraint fails stating that category which you are trying to delete is associated with other product.
You should be very careful while mentioning cascade type. By default cascade mode is NONE but you can give different strategies like UPDATE, PERSIST etc. based upon your requirement

Related

Problem while creating multiple foreign keys on a compound primary key using JPA

I have a table structure like this:
Table Structure
The dependencies are unidirectional.
My POJO classes look like this:
class T1 {
#EmbeddedId T1PK t1Pk;
String c13;
// getter setter
}
#Embeddable
class T1PK {
String c11;
String c12;
// getter setters hashCode equals
}
class T3 {
#Id String T31
String T32
// getter setter
}
#Embeddable
class T2PK {
T1PK t1Pk;
String c31;
// getter setters hashCode equals
}
class T2 {
#EmbeddedId T2PK t2Pk;
#MapsId("c31")
#JoinColumn(name = "c31", referencedColumnName = "c31", foreignKey = #ForeignKey(name = "FK_32"))
#OneToOne
private T3 t3;
#MapsId("t1Pk")
#JoinColumns(foreignKey = #ForeignKey(name = "FK_12"), value = {
#JoinColumn(name = "c11", referencedColumnName = "c11"),
#JoinColumn(name = "c12", referencedColumnName = "c12") })
#OneToOne
private T1 t1;
// getter setter
}
When I am starting my spring boot application, the following error is being encountered
Foreign key (FKakdpaxm8kl8mv0ld210dg7mcw:t2 [c11,c12,c31])) must have same number of columns as the referenced primary key (t3 [t31])
However it works fine if I just comment out the dependency of any one table, t3 or t1. A proper foreign key with the mentioned FK name gets created.
I wasn't able to try this out but T2PK should not contain a reference to T1PK but rather the #OneToOne relationship to T1 that is currently placed in T2.
#Embeddable
class T2PK {
#JoinColumns(foreignKey = #ForeignKey(name = "FK_12"), value = {
#JoinColumn(name = "c11", referencedColumnName = "c11"),
#JoinColumn(name = "c12", referencedColumnName = "c12") })
#OneToOne
private T1 t1;
String c31;
}
class T2 {
#EmbeddedId
T2PK t2Pk;
#MapsId("c31")
#JoinColumn(name = "c31", referencedColumnName = "c31", foreignKey = #ForeignKey(name = "FK_32"))
#OneToOne
private T3 t3;
}
Edit: It also may be possible that you can place the #OneToOne to T1 in T2 and use a #MapsId without value or a #MapsId("t1") to map the whole T1 object into your T2PK but I am not sure.

Cannot delete parent and child

I have two entities using Spring and Hibernate
Entity A:
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "a")
private B b;
Here i have a one to one relationship with Entity B, the owner of the relationship is Entity A.
Entity B:
#Entity
public class B{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToOne
#JoinColumn(name = "a_ID")
private A a;
Saving the entities gives no problem. Entity B gets the ID of entity A in the Database.
But when i delete Entity A, I also want to delete Entity B that belongs to A.
When I delete i get the error:
MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
I think my declarations in my entities are correct. What is the problem here?
Edit
When I inspect the table of Entity B and specifically the Foreign key to Entity A it says Restricted on Update en on Delete
set orphanRemoval property to true in class A
like this: #OneToOne(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true)

Alternative for #Formula?

I have two objects:
#Table(name = "user")
User
#Id
Integer id
Integer uuid;
and reservation:
#Table(name = "reservation")
Reservation
#Id
Integer id;
Integer uuid;
My goal is:
#Table(name = "reservation")
Reservation
#Id
Integer id;
Integer uuid;
#Formula("(SELECT * FROM user b WHERE b.uuid = uuid )")
List<User> users;
The problem is #Formula doesnt work with objects.
How to include list of all users in reservation with same uuid?
I found this but maybe there is better option
https://stackoverflow.com/a/37502703/3871754
Support of relationships that references non-PK columns is an optional feature. In simple cases it's supported by Hibernate
#NotAudited
#OneToMany
#JoinColumn(name = "uuid", referencedColumnName = "uuid")
private List<Barrier> barriers = new ArrayList<>();
and implemented Serializable for Reservation

Many to Many child removel JPA

Hi i have many to many relationship and entities are here:
#Entity
public class Company implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer idcompany;
//bi-directional many-to-many association to Client
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="company_client",
joinColumns=#JoinColumn(name="company_idcompany",
referencedColumnName="idcompany"),
inverseJoinColumns=#JoinColumn(name="client_idclient",
referencedColumnName="idclient"))
Set<Client> clients = new HashSet<Client>();
and second entity
#Entity
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int idclient;
#ManyToMany(mappedBy="clients",cascade = CascadeType.PERSIST , fetch =
FetchType.LAZY )
Set<Company> companies = new HashSet<Company>();
and here is details of date in all 3 tables
---------------------------------------------
company table client table company_client table
id name id name fk_compny fk_client
1 Oracle 1 Abc 1 1
2 XYZ 1 2
when i want to remove row from client table e.g "XYZ" id=2
Client cl = em.find(Client.class,2);
em.remove(cl);
it throws exception "Cannot delete or update a parent row: a foreign key constraint fails " .
How to remove this row

How to perform a cascaded merge against child of new entity with JPA and Hibernate

This question relates to managing id numbers to ensure that I don't end up with duplicate identities on my person's table. It is a springboot project with MySQL database.
Some background:
I have an HTML form for submitting an "episode". Each episode contains "persons" and has a relationship to "persons" of ManyToMany.
"episodes" are entered and submitted into the database (db1) by field staff. A few hours later the episode is manually entered into a second database (db2) by BackOffice staff.
On my spring attached database (db1) I have a persons table which has a native auto generated id field. db1 also has a id2 field - which records the unique id for the person from db2.
Field staff do not always have access to id2 when they enter a episode, but BackOffice staff do.
When I save a new "episode" I need the save method to check if person id2 exists in the database and perform an update on person (not create new).
Then delete the duplicate person.
Episode entity:
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#Column(name="start_date")
#DateTimeFormat (pattern="dd/MM/yyyy HH:mm")
private Date start_date;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "episode_person", joinColumns = #JoinColumn(name = "episode_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "person_id", referencedColumnName = "id"))
private List<Person> persons;
#OneToOne(cascade=CascadeType.ALL)
//#JoinColumn(name = "id")
private Address address;
Person Entity
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long id2;
private String surname;
private String firstname;
private String phoneHome;
#DateTimeFormat(pattern = "dd/mm/yyyy")
private Date dob;
#ManyToMany(mappedBy = "persons", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Episode> episodes;
EpisodeServiceImpl
#Override
#Transactional
public Episode saveEpisode(Episode episode) {
List mergedPeople = personService.mergeDetachedWithAttached( episode.getPersons() );
episode.setPersons( mergedPeople );
return episodeRepository.save(episode);
}
PersonServiceImpl
#Transactional
#Override
public List mergeDetachedWithAttached(List<Person> people) {
final List<Person> results = new ArrayList<>();
if ( people != null && !people.isEmpty() ) {
// loop over every person
for (Person person : people) {
// for current person try to retrieve from db via Id2
Person db2Person = personRepository.findById2( person.getId2() );
// if a person was retrieved use them instead of submitted person
if (db2Person != null ) {
System.out.println("A matching person was found in the db using id2 - this is the person that will be added");
results.add(db2Person);
} else {
results.add(person);
}
}
}
return results;
The way this is written at the moment when ever a new episode is submitted I create new person(s) even if I successfully looked them up from db1 using id2 and added them to the episode.
How can I handle this so that:
I can merge duplicate identities based on comparing id2. The joining table that holds episode_id and person_id will also need to be updated where a id is deleted after a merge.
It's much easier if you replace the #ManyToMany association with 2 bidirectional #OneToMany associations, meaning that you map the association table as well.
This way, considering that you have those duplicated Episode or Person entities, you can easily move the joined association by simply adjusting the #ManyToOne associations on the association entity. As for the duplicated Episode or Person you can either use UPSERT as explained below, or do a manual merging after the entries are added to the DB by the batch process.
Typically, when you have multiple nodes that running concurrently, you can use the database UPSERT or MERGE operation.
You could combine the UPSERT with Hibernate #SqlInsert annotation.
To handle the FK updates, you'd need to use the FK ON DELETE CASCADE strategy.