I have three classes: Location, MTFCC, and BorderPoint.
Location has a unidirectional #ManyToOne relationship with MTFCC, which is intended only as a Lookup table. No cascading is defined.
Location also has a bidirectional #ManyToOne/#OneToMany with BorderPoint. Since I want all associated BorderPoint objects to delete when I delete a Location, I set cascadetype.ALL on the Location side of the relationship.
Unfortunately, an EntityExistsException is being thrown when I attempt to delete a location:
org.apache.openjpa.persistence.EntityExistsException: Cannot delete or update
a parent row: a foreign key constraint fails (`mapmaker`.`BORDERPOINT`,
CONSTRAINT `BORDERPOINT_ibfk_1` FOREIGN KEY (`LOCATIONID`) REFERENCES `LOCATION`
(`LOCATIONID`)) {prepstmnt 21576566 DELETE t0, t1 FROM LOCATION t0 INNER JOIN
MTFCC t1 ON t0.MTFCCID = t1.MTFCCID WHERE (t0.STATEFP = ? AND t1.MTFCCCODE = ?)
[params=?, ?]} [code=1451, state=23000]
[ERROR] FailedObject: DELETE t0, t1 FROM LOCATION t0 INNER JOIN MTFCC t1 ON
t0.MTFCCID = t1.MTFCCID WHERE (t0.STATEFP = ? AND t1.MTFCCCODE = ?)
[java.lang.String]
It looks like it's attempting to delete the associated MTFCC object which I do NOT want to happen. I do, however, want the associated BorderPoint objects to be deleted.
Here is the code (chopped down a bit):
#SuppressWarnings("unused")
#Entity
#Table(name="LOCATION")
#DetachedState(enabled=true)
public class Location implements Serializable, IsSerializable, Cloneable {
private Long id;
private String stateGeoId;
private MTFCC mtfcc;
private List<BorderPoint> borderPointList;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="LOCATIONID")
public Long getId() {
return id;
}
#ManyToOne
#JoinColumn(name="MTFCCID")
public MTFCC getMtfcc() {
return mtfcc;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "location", fetch = FetchType.EAGER)
public List<BorderPoint> getBorderPointList() {
return borderPointList;
}
}
#Entity
#Table(name = "BORDERPOINT")
#DetachedState(enabled = true)
public class BorderPoint implements Serializable, IsSerializable {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="BORDERID")
public Long getId() {
return id;
}
#ManyToOne(targetEntity = Location.class)
#JoinColumn(name="LOCATIONID")
public Location getLocation() {
return location;
}
}
#Entity
#Table(name = "MTFCC")
public class MTFCC implements Serializable, IsSerializable {
private Long id;
private String mtfccCode;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MTFCCID")
public Long getId() {
return id;
}
// etc
}
And, for good measure, here is the deletion code:
#Override
#Transactional
public int removeByStateGeoIdAndMtfcc(String stateGeoId, String mtfccCode) throws RepositoryException {
EntityManager em = entityManagerFactory.createEntityManager();
String jpaQuery = "DELETE FROM Location L where L.stateFP = ?1 AND L.mtfcc.mtfccCode = ?2";
int affectedRows = 0;
Query query = em.createQuery(jpaQuery).setParameter(1, stateGeoId).setParameter(2, mtfccCode);
try {
em.getTransaction().begin();
affectedRows = query.executeUpdate();
em.getTransaction().commit();
} catch (Exception e) {
//log.debug("Exception: ", e);
throw new RepositoryException(e);
}
em.close();
return affectedRows;
}
Hopefully I copied all relevant parts... can anyone assist?
You aren't reading the error message correctly. It says that the deletion is forbidden because of the foreign key constraint between BorderPoint and Location.
The cascade delete would work if you used em.remove(location) to delete your Location. Using a delete query like you're doing won't automagically delete the BorderPoints before deleting the location.
Either load them and remove them using em.remove, or execute other delete queries before to delete the BorderPoints.
Related
I am newbie on Hibernate,
I want to perform deletion on one table the current table is referenced to another table.
Ex: Client_Loan is my main table i want to perform deletion on this, and the another table is 'Loan_Details'. the loan_details table contains the foreign_Key reference to the client_loan id.
i want to perform delete on client_loan table, if i delete client loan table the references in another table also want to delete at one shot using Hibernate can any one tell me how to perform.
Thanks in advance.
You must be having classes like below
#Entity
#Table(name="Client_Loan")
public class ClientLoan {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "client", orphanRemoval = true)
private List<Loan_Details> loans = new ArrayList<>();
public void setName(String name) {
this.name = name;
}
public List<Loan_Details> getComments() {
return loans;
}
public void addComment(Loan_Details loan) {
loans.add(loan);
}
}
below for load details
#Entity
#Table(name="Loan_Details ")
public class LoanDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private Client_Loan client;
public void setPost(Client_Loan post) {
this.post = post;
}
public String getReview() {
return review;
}
public void setReview(String review) {
this.review = review;
}
}
Now in ClientLoan in OnetoMany mapping cascade = CascadeType.ALL
it means when you will delete CleintLoan all the LoanDetails will be deleted automatically
First delete from Loan_Details then Client_loan
First delete from Loan_Details then Client_loan
int id = 2;
Session session=getSession();
String hql = "delete from Loan_Details where client_loanid = :id";
session.createQuery(hql).setString("id", new Integer(id)).executeUpdate();
hql = "delete from Client_Loan where clientid = :id";
session.createQuery(hql).setString("id", new Integer(id)).executeUpdate();
Owner:
#Entity
public class Strategy implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
#JoinTable(name = "StrategyHost", joinColumns = {#JoinColumn(name = "strategyId")}, inverseJoinColumns = {#JoinColumn(name = "hostId")})
private Set<Host> hostName;
}
Related entity:
#Entity
public class Host {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String name;
#ManyToMany(mappedBy = "hostName")
private List<Strategy> strategies;
public Host(String name) {
this.name = name;
}
}
Test:
#Test
#Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testStrategyWithHosts() {
Strategy s = new Strategy();
Set<Host> hosts= new HashSet<>();
hosts.add(Host.builder().name("aaa").build());
hosts.add(Host.builder().name("bbb").build());
s.setHostName(hosts);
Strategy saved= strategyDao.save(s);
Set<Host> hostName = saved.getHostName();
}
debug shows the persisted saved object having Host:
Where are name values? However, if I add merge in cascade type array, name are valued. Why insert (not update managed entities) operation for related entities must have merge cascade type? Although log shows nothing suspicious:
insert into strategy...
insert into host...
insert into host...
update strategy ...
insert into strategy_host ...
insert into strategy_host ...
I've seen other posts about this problem, but have found no answer to my own troubles. I have
#Entity
#Table(name= ServerSpringConstants.COMPANY)
public class Company implements Serializable {
private static final long serialVersionUID = -9104996853272739161L;
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column (name = "companyID")
private long companyID;
#OneToMany (targetEntity = Division.class, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch = FetchType.EAGER)
#JoinTable (name = "companyDivisionJoinTable",
joinColumns = #JoinColumn(name="companyID"),
inverseJoinColumns = #JoinColumn(name="divisionID")
)
private Set<Division> divisions = new HashSet<>();
public long getCompanyID() {
return companyID;
}
public Set<Division> getDivisions() {
return divisions;
}
public void setDivisions(Set<Division> divisions) {
this.divisions = divisions;
}
}
On the other side:
#Entity
#Table(name= ServerSpringConstants.DIVISION)
public class Division implements Serializable {
private static final long serialVersionUID = -3685914604737207530L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "divisionID")
private long divisionID;
#ManyToOne
(fetch = FetchType.LAZY, optional = false, targetEntity = Company.class,
cascade = {CascadeType.PERSIST, CascadeType.MERGE
}
)
#JoinColumn(name="companyID", referencedColumnName = "companyID")
private Company company;
public long getDivisionID() {
return divisionID;
}
public void setDivisionID(long divisionID) {
this.divisionID = divisionID;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Yet for some reason, LAZY loading not working. I'm using JPA. I'm calling back the companies, and their enclosing divisions from within a 'User' class -- the pertinent part
#ManyToMany (targetEntity = Company.class,
cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch=FetchType.EAGER )
#JoinTable (
name="companyUserJoinTable",
joinColumns=#JoinColumn(name="userID"),
inverseJoinColumns=#JoinColumn(name="companyID")
)
private Set<Company> company = new HashSet<>();
I've searched out existing threads, and have tried adding various suggestions, but nothing has helped!
Any help appreciated.
Thanks!
Since you are loading the divisions set eagerly (with fetch = FetchType.EAGER) and you have a bidirectional association, divisions will be initialized with the parent reference to company. I can't see any problem with it. Jpa loaded the full object tree because you just told it so. A company contains divisions which contain a back reference to the company that loaded them.
To understand it better, since the reason for lazy loading is to reduce the data loaded from database, the owning company is already loaded in session for the divisions, so why not setting the association too?
The #ManyToOne association on the other side takes effect if you load divisions first.
To be more correct with your mapping add also a #MappedBy attribute to the one part of the association. This does not affect fetching behavior but will prevent double updates to the database issued by both ends of the association.
the old issue description is obsolete
#unwichtich thank you for your tip, it helped get rid of that nasty error.
I have the entities:
#Entity
#XmlRootElement
#Table(name="WAITERENTITY")
public class WaiterEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.EAGER)
#JoinColumn(name = "waiter_id")
private List<OrderEntity> orders = new ArrayList<>();
{plus usual setters and getters}
}
and
#Entity
#XmlRootElement
#Table(name="ORDERENTITY")
public class OrderEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long orderNumber;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "table_id")
private TableEntity table_;
private int sumOfMoney = 0;
private boolean finalized = false;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private List<OrderItemEntity> orderItems = new ArrayList<>();
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "waiter_id")
private WaiterEntity waiter;
{plus usual setters and getters}
}
But the main problem remains. In the database, everything is as it should be:
<waiterEntities>
<waiterEntity>
<id>9</id>
<name>Jack Marston #499</name>
</waiterEntity>
<waiterEntity>
<id>10</id>
<name>Abigail Marston</name>
</waiterEntity>
</waiterEntities>
<orderEntities>
<orderEntity>
<finalized>false</finalized>
<orderNumber>12</orderNumber>
<sumOfMoney>0</sumOfMoney>
<waiter>
<id>9</id>
<name>Jack Marston #499</name>
</waiter>
</orderEntity>
</orderEntities>
But the #OneToMany relation of WaiterEntity does only return an empty list when waiter.getOrders() is called.
The method that creates a new OrderEntity is the following:
public void create(OrderEntity e) {
WaiterEntity waiter = em.find(WaiterEntity.class, e.getWaiter().getId());
if (waiter != null) {
(1) e.setWaiter(waiter);
em.persist(e);
System.out.println("after persist:\n" + e);
(2) //waiter.getOrders().add(e);
(3) //em.merge(waiter);
}
}
Edit: I observed very strange behaviour. Firstly, if the lines marked with (2) and (3) are un-commented, no OrderEntity will be persisted at all. Secondly, only the following outside statements will suffice GlassFish to persist an OrderEntity:
WaiterBean waiter = client.findByNameSingle(WaiterBean.class, "John Marston");
client.create(new OrderBean(waiter));
Where create will get an unique id of the respective WaiterEntity from the database. On the other hand, an OrderEntity will be not persisted, if no WaiterEntity id is known, as in for example:
client.create(new OrderBean(new WaiterBean("Hans")));
because this new object is not obtained from the database. The strage behaviour appears, when line marked with (1) is commented out: the first statement, with the previous obtainment of the respective WaiterEntity from the database won't work, but the second statement, that doesn't obtain any WaiterEntity from the database, will work and create an OrderEntity entry in the database. I really have a hard time understanding that.
The two commented lines (2) and (3) should assure that the WaiterEntity knows its OrderEntitys for later retrieval. But the only thing that these two lines do (or one of them, i tried that as well) is preventing any OrderEntity to be persisted into the database. It just won't do anything, and no further errors are reported, which drives me nuts...
Any ideas? Thanks in advance!
Just a quick guess is that you are adding the wrong OrderEntity instance. If you pass an entity instance to em.merge() the EntityManager creates a new instance of your entity, copies the state from the supplied entity, and makes the new copy managed. You have to use the new copy in any further actions regarding this entity.
In code:
public void create(OrderEntity e) {
WaiterEntity waiter = em.find(WaiterEntity.class, e.getWaiter().getId());
e = em.merge(e);
if (waiter != null) waiter.getOrders().add(e);
}
My provider is:
org.eclipse.persistence.jpa.PersistenceProvider
My Entity code is:
#Entity
#NamedQueries( { #NamedQuery(name = "Bank.findAll", query = "select o from Bank o") })
#Table(name = "B_BANK")
public class Bank implements Serializable {
private final static String ID_GENERATOR = "Bank";
private Long id;
private User modifier;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = ID_GENERATOR)
#SequenceGenerator(name = ID_GENERATOR, allocationSize = 1, sequenceName = "sq_B_BANK")
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "modifier")
public User getModifier() {
return modifier;
}
public void setModifier(User modifier) {
this.modifier = modifier;
}
}
And my test code is just:
entityManagerHelper = new EntityManagerHelper("Model", isAutoCommit);
List l = entityManagerHelper.getEntityManager().createQuery("select b from Bank b").getResultList();
However in my console I had this:
SELECT id, modifier FROM B_BANK
SELECT id, CREATION_DATE, MODIFICATION_DATE, modifier, creator FROM B_USER WHERE (id = ?)
bind => [2]
Therefore it seems that JPA queried for the User that I put fetch = FetchType.LAZY over it!
I am wondering why JPA queried for the User too.
Any help would be appreciated.
While the answer is true, EclipseLink uses weaving to implement lazy fetching for OneToOne and ManyToOne relationships. If you are not run in a container environment, then you would have to add in an agent or setup your entities to be statically woven to use lazy on 1:1s as described here: http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Performance/Weaving/Dynamic_Weaving and here http://www.eclipse.org/eclipselink/documentation/2.4/concepts/app_dev007.htm