I am having a specific scenario, where 2 tables are having parent child relationship. When i m invoking the api to delete a child, it works fine. And then when I try to invoke the api to delete the parent, I get optimistic lock exception.
Upon investigation, I realized that while deleting the parent, it is trying to delete the child as well which has already been deleted.
Below is my code snippet. Any suggestion or help would be great.
This is the code for deleting the child.
#Transactional(Transactional.TxType.MANDATORY)
public void deleteChild(int id) {
var child= entityManager.find(Child.class, id);
entityManager.remove(child);
}
And this is the code for deleting the parent.
#Transactional(Transactional.TxType.MANDATORY)
public void deleteParent(int id) {
var parent = entityManager.find(Parent.class, id);
entityManager.remove(parent);
}
The Parent entity has OneToMany relationship with the Child entity.
#Table(name = "PARENT")
#Access(value = AccessType.FIELD)
public class Parent{
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "PARENT_ID")
private List<Child> child;
}
Related
I have this query
DELETE
FROM bookings as b
WHERE b.check_out = CURRENT_DATE;
and I get
Cannot delete or update a parent row: a foreign key constraint fails (online_booking_app.booked_rooms, CONSTRAINT FK3x1lpikb2vk75nx41lxhdicvn FOREIGN KEY (booking_id) REFERENCES bookings (id))
My Booking entity has CascadeType.ALL and mapped by matches the other side - from my research these are some of the mistakes that could lead to this message.
Here is the BookingEntity:
#Entity
#Table(name = "bookings")
public class BookingEntity extends BaseEntity {
#OneToMany(mappedBy = "booking",cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookedRoomsEntity> bookedRooms = new ArrayList<>();
private String firstName;
private String lastName;
public List<BookedRoomsEntity> getBookedRooms() {
return bookedRooms;
}
public BookingEntity setBookedRooms(List<BookedRoomsEntity> bookedRooms) {
this.bookedRooms = bookedRooms;
return this;
}
BookedRoomsEntity
#Entity
#Table(name = "booked_rooms")
public class BookedRoomsEntity extends BaseEntity {
#ManyToOne()
private BookingEntity booking;
public BookingEntity getBooking() {
return booking;
}
public BookedRoomsEntity setBooking(BookingEntity booking) {
this.booking = booking;
return this;
}
The CascadeType does only apply to EntityManager operations.
You therefore have two options:
Load the entities to be deleted first and then use EntityManager.remove
Remove the referencing entities first with a separate JPQL statement.
I have a Parent entity contains a list of Childs.
Parent A contains Child A1
In Transaction
Add Child A2 to the child list
Save (without Flush)
Clear the child list (by clear collection)
Add Child A3
Save and Flush
In the result:
There are two Child records in database: (A1 is removed)
A2 with no Parent info (==null)
A3 with Parent info
Can someone point me a document to show how JPA works not to add Parent info for A2?
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.SELECT)
#JoinColumn(name = "parent_id")
List<Child> children;
#Version
private Long recordVersion;
}
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "parent_id", insertable = false, updatable = false)
private Parent parent;
#Version
private Long recordVersion;
public String toString() {
return "Child[id=" + id + ";name=" + name + "]";
}
}
#Transactional
public void addChild(Long parentId, String childName) {
Parent parent = getParent(parentId);
Child child = new Child();
child.setName("A2");
List<Child> children = Arrays.asList(child);
parent.getChildren().addAll(children);
parentRepository.save(parent);
parent = getParent(parentId);
parent.getChildren().clear();
Child child1 = new Child();
child1.setName("A3");
List<Child> spaces1 = Arrays.asList(child1);
parent.getChildren().addAll(spaces1);
parentRepository.saveAndFlush(parent);
}
Update 1:
if I comment out parentRepository.save(parent); or replace by parentRepository.saveAndFlush(parent);, there is only A3 in the database.
The most likely reason for the issue you're facing is that the bidirectional association is not mapped correctly. In fact, you've declared two completely unrelated associations, and then you made them share the join column.
To properly map a bidirectional one-to-many association, you need to declare the 'one' side as being the inverse side like so:
#OneToMany(mappedBy = "parent")
#Fetch(FetchMode.SELECT)
List<Child> children;
Note that, in such a scenario, Child is the owning side of the association. Changes to Parent.children are completely ignored.
Since you seem to want to be able to manage the association using Parent.children exclusively, you'll probably want to make Parent the owning side by removing the Child.parent property instead (thus changing the association to a unidirectional one).
Note that the two above options are the only valid mappings. You cannot simultaneously make both sides of an association the owning side, and in a bidirectional one-to-many association, the 'one' side cannot be the owning side.
I have Employee and Functions, Bank classes
Employee and Function have #OneToMany relationship and
Employee and Bank have also #OneToMany relationship.
if the user edits the form and change the function and/or bank
I want to update the relationship. but when I change the relationship
I get Duplicate entry exception due to the uniqueness of a column because
the Employee object persisted as a new entity
I tried to remove the employee from the function and set the employee's function to null and get a new function and add the employee to it
and set the new function but it doesn't work. any idea, please
#Entity
public class Employee extends GeneratedIdEntity<Long> {
#ManyToOne(optional = false)
private Functions function;
#ManyToOne(optional = false)
private Bank bank;
#OneToMany(
mappedBy = "employee",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<RubricValue> rubricsValues = new ArrayList<>();
#OneToMany(
mappedBy = "employee",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
List<EmployeeStatus> employeesStatus=new ArrayList<>();
}
#Entity
public class Functions extends GeneratedIdEntity<Long>{
#OneToMany(
mappedBy = "function",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<Employee> employees=new ArrayList<>();
public void addEmployee(Employee employee ){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
}
#Entity
public class Bank extends GeneratedIdEntity<Long> {
#OneToMany(
mappedBy = "bamk",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee employee ){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
}
#Stateless
public class EmployeeService extends BaseEntityService<Long, Employee> {
#Inject
FunctionService functionService;
#Inject
BankService bankService;
public void update(Employee employee, String newFunctionName, String newBankName) {
if (!employee.getBank().getName().equals(newBankName)) {
employee.getBank().removeEmployee(employee);
employee.setBank(null);
Bank newBank = bankService.getByName(newBankName);
newBank.addEmployee(employee);
employee.setBank(newBank);
}
if (!employee.getFunction().getName().equals(newFunctionName)) {
employee.getFunction().removeEmployee(employee);
employee.setFunction(null);
Functions newFunction = functionService.getByName(newFunctionName);
newFunction.addEmployee(employee);
employee.setFunction(newFunction);
}
}
}
the exception stack trace Caused by:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry
'dkfhks32' for key 'REGISTRATIONNUMBER' at
com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:115)
at
com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95)
at
com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:960)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1116)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1066)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1396)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1051)
at
com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:127)
at sun.reflect.GeneratedMethodAccessor54.invoke(Unknown Source) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
com.sun.gjc.spi.jdbc40.ProfiledConnectionWrapper40$1.invoke(ProfiledConnectionWrapper40.java:437)
at com.sun.proxy.$Proxy268.executeUpdate(Unknown Source) at
org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:898)
You don't have to change the bank for an employee by removing the reference of existing bank first. You can simply go:
if (!employee.getBank().getName().equals(newBankName)) {
Bank newBank = bankService.getByName(newBankName);
//You must also do an entity validation/null check here. The newBank might not be present after all.
employee.setBank(newBank);
}
This will update the mappings correctly. Same goes for updating function of an employee
i have the following entity relationship:
SideA:
#Entity
#Table(name = "SideA")
public class SideA {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CascadeOnDelete
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sideA", cascade=CascadeType.ALL)
private List<ABAssociation> association = new ArrayList<ABAssociation>();
}
Side B:
#Entity
#Table(name = "SideB")
public class SideB {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CascadeOnDelete
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sideB", cascade=CascadeType.ALL)
private List<ABAssociation> association = new ArrayList<ABAssociation>();
}
ABAssociation:
#Entity
#Table(name = "ABAssociation")
public class ABAssociation {
#EmbeddedId
private ABAssociationPK pk = new ABAssociationPK();
#ManyToOne(cascade=CascadeType.MERGE)
#MapsId("aId")
private SideA sideA;
#ManyToOne(cascade=CascadeType.MERGE)
#MapsId("bId")
private SideB sideB;
}
ABAssociationPK:
#Embeddable
public class ABAssociationPK implements java.io.Serializable{
private long aId;
private long bId;
}
my problem is when i delete one side, the database delete the row in ABAssociation , but still stay in cache.
test code is like the follow:
SideA a = new SideA();
SideB b = new SideB();
entitymanager.persist(a);
entitymanager.persist(b);
ABAssociation ab = new ABAssociation()
ab.setSideA(a);
ab.setSideB(b);
entitymanager.persist(ab);
a.getABAssociationList().add(ab);
b.getABAssociationList().add(ab);
a = entitymanager.merge(a);
b = entitymanager.merge(b);
entitymanager.delete(a);
Since "a" was deleted, the relationship between "a" and "b" should be deleted too.
but when i check the "b.getABAssociationList().size()" it still there, even there is no rows in ABAssociation table in DB.
it this related to the share cache issue ?
In JPA you must maintain you object's relationships.
If you remove an object, you must first remove all references to it.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side
somewhat, yes. You need to remove B's reference to ABAssociation when ABAssociation gets deleted to maintain the cache with what is in the database. You might do this by using a preremove event on ABAssociation if you cannot do it in your application.
you can force update your list using evict() like this:
getEntityManager().getEntityManagerFactory().getCache().evict(ABAssociation.class);
I have ManyToOne relationship between Parent and Child. I want to Delete multiple Child entities that answer a certain query.
The problem is that after I run a Delete query, Parent.getChildren() still returns the deleted children.
Can't I use Delete queries in such case?
#Entity
#Table(name = "CHILD_DATA")
public class Child {
private Parent parent;
}
#Entity
#Table(name = "PARENT")
public class Parent{
private Set<Child> children;
#Column(name = "CHILDREN")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
public Set<Child> getChildren() {
return children;
}
}
public class ChildDAO{
public int removeServiceFrontPageData(Parent parent, long serviceID){
String query = "DELETE FROM Child WHERE parent =:parent";
Query q = em.createQuery(query);
q.setParameter("parent", parent);
return q.executeUpdate();
}
}
To refresh a parent entity, I use the following function:
public class ParentDAO{
public Parent getParent(String parentID){
final String select = "FROM Parent WHERE parentID = :parentID";
Query q = em.createQuery(select);
q.setParameter("parentID", parentID);
if(q.getResultList().isEmpty()){
return null;
}
return (Parent) q.getSingleResult();
}
}
thanks
My solution is based on another post
I found 2 ways to solve it:
1) update a parent if I remove children:
select child entities that should be removed.
remove these entities from parent.getChildren()
delete the children from database
2) add orphanRemoval=true flag on parent.getChildren(). Remove children from set will remove them from database
#Entity
#Table(name = "PARENT")
public class Parent{
private Set<Child> children;
#Column(name = "CHILDREN")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public Set<Child> getChildren() {
return children;
}