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);
}
Related
I am starting with JPA and EclipseLink and did some experiments. But results are a little unexpected for my understanding. Here for example I have a bidirectional relation between two entities:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Tuition tuition;
#Entity
#Table(name = "tuition")
public class Tuition {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long fee;
// Que columna en la tabla Tuition tiene la FK
#JoinColumn(name = "student_id")
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Student student;
I have created two tests to check persisting both entities from both sides. When I try to persist from one side it works OK, both entities are persisted. But when trying to do the same from the other side Student in Tuition have a null value
Here are the tests:
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Jpa1to1Bidireccional {
private static final String PERSISTENCE_UNIT_NAME = "JPA_PU";
private EntityManagerFactory factory;
#Before
public void init() throws Exception {
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
}
#Test
public void testCase_1() {
System.out.println("Testcase_1 executes");
EntityManager em = factory.createEntityManager();
// Begin a new local transaction so that we can persist a new entity
em.getTransaction().begin();
Student s = new Student();
s.setName("Pirulo");
Tuition t = new Tuition();
t.setFee(2134L);
s.setTuition(t);
em.persist(s);
// Commit the transaction, which will cause the entity to // be stored in the
// database
em.getTransaction().commit();
em.close();
}
#Test
public void testCase_2() {
System.out.println("Testcase_2 executes");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student s = new Student();
s.setName("Cachilo");
Tuition t = new Tuition();
t.setFee(3134L);
t.setStudent(s);
em.persist(t);
em.getTransaction().commit();
em.close();
}
Any help to understand what is going on, will be very welcomed.
Regards,
In your first case you are persisting student and then cascading a save to tuition but tuition has no student. You need to keep bidirectional relationships in sync before persisting
Second case you persist tuition and it requires fk of student so it cascade persist student first
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.
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 an entity class that contains a map of key-value pairs which live in a different table and there may be no such pairs for a given entity. The relevant code for the entity classes is below.
Now, when I insert such an entity with persist(), then add key-value pairs, and then save it with merge(), I get duplicate entry errors for the related table that stores the key-value pairs. I tried to hold back insertion until the keys were added, to have one call to persist() only. This led to duplicate entry errors containing an empty (zero) id in the foreign key column (ixSource).
I followed the process in the debugger, and found that eclipselink seems to be confused about the cascading. While it is updating the entity, it executes calls that update the related table. Nonetheless, it also adds those operations to a queue that is processed afterwards, which is when the duplicate entry errors occur. I have tried CascadeType.ALL and MERGE, with no difference.
I'm using static weaving, if it matters.
Here's the entities`code, shortened for brevity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "sType")
#Table(name = "BaseEntity")
public abstract class BaseEntity extends AbstractModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ix")
private long _ix;
}
#Entity
#Table(name = "Source")
public class Source extends BaseEntity
{
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "SourceProperty", joinColumns = { #JoinColumn(name = "ixSource") })
#MapKey(name = "sKey")
private Map<String, SourceProperty> _mpKeys;
// ... there's more columns that probably don't matter ...
}
#Entity
#Table(name = "SourceProperty")
#IdClass(SourcePropertyKey.class)
public class SourceProperty
{
#Id
#Column(name = "sKey", nullable = false)
public String sKey;
#Id
#Column(name = "ixSource", nullable = false)
public long ixSource;
#Column(name = "sValue", nullable = true)
public String sValue;
}
public class SourcePropertyKey implements Serializable
{
private final static long serialVersionUID = 1L;
public String sKey;
public long ixSource;
#Override
public boolean equals(Object obj)
{
if (obj instanceof SourcePropertyKey) {
return this.sKey.equals(((SourcePropertyKey) obj).sKey)
&& this.ixSource == ((SourcePropertyKey) obj).ixSource;
} else {
return false;
}
}
}
I can't see how those errors would occur. Could you include the SQL and ful exception.
What version of EclipseLink are you using, did you try the latest release?
Why are you calling merge? Are you detaching the objects through serialization, if it is the same object, you do not need to call merge.
It could be an issue with the #MapKey, does it work if you remove this?
Customer Entity (Parent Entity)
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy="customer", cascade=CascadeType.ALL)
private List<Facility> facilities;
//Setter and Getter for name and facilities
public void addFacility(Facility facility){
if(this.facilities == null){
this.facilities = new ArrayList<Facility>();
}
this.facilities.add(facility);
facility.setCustomer(this);
}
}
Facility Entity (Child Entity)
#Entity
public class Facility {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name="CUSTOMER_FK")
private Customer customer;
private String name;
//Setter and Getter, equals and hashcode
...
}
in Customer entity, I use CascadeType.ALL, however when I remove a customer, the associated facilities are still there. I delete customer by
Query query = em.createNamedQuery("Customer.delete");
query.setParameter("id", customerId);
query.executeUpdate();
where
#NamedQuery(name="Customer.delete", query="delete from Customer c where c.id = :id")
Bulk delete operations are not cascaded, per JPA specification:
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.
...
If you want to benefit from cascading, load the entity and then call EntityManager#remove(Object) on it.
Try with:
#Inject
EntityManager em;
Customer customer =...;
em.remove(customer);
This always cascades operations.