Hibernate parent/child relationship. Why is object saved twice? - java

I am looking into Hibernate's parent/child relationships.
I have 3 entities Employee Customers and Orders.
Their relationship is
Employee 1 <-> N Orders
Customer 1 <-> N Orders
I want to be able to save/update/delete Customer objects and Employees and Orders but I want to use some interface so that the calling code does not deal with any Hibernate or JPA stuff.
E.g. I tried something like the following:
class Utils{
public static void saveObject(Object o){
logger.debug(o.toString());
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.save(o);
tx.commit();
session.close();
}
}
and in the calling code I do:
Employee employee = new Employee();
//set data on employee
Customer customer = new Customer();
//set data on customer
Order order = new Order();
//set data on order
employee.addOrder(order);//this adds order to its list, order gets a reference of employee as parent
customer.addOrder(order);//this adds order to its list, order gets a reference of customer as parent
Utils.saveObject(customer);
Utils.saveObject(employee);
Now I noticed that with this code, 2 records of employee are created instead of 1.
If I only do:
Utils.saveObject(customer);
Only 1 (correctly) record is created.
Why does this happen?
Does this happens because the same Order object is saved by both Customer and Employee? And the cascade="all" makes this side-effect?
Now if I do not use the DBUtils method and do directly:
Session session = DBUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.save(employee);
session.save(customer);
tx.commit();
session.close();
Again, it works as expected. I.e. only 1 employee record is created.
What am I doing something wrong here?
UPDATE:
Hibernate mappings:
<hibernate-mapping>
<class name="database.entities.Associate" table="EMPLOYEE">
<id name="assosiateId" type="java.lang.Long">
<column name="EMPLOYEEID" />
<generator class="identity" />
</id>
<property name="firstName" type="java.lang.String" not-null="true">
<column name="FIRSTNAME" />
</property>
<property name="lastName" type="java.lang.String" not-null="true">
<column name="LASTNAME" />
</property>
<property name="userName" type="java.lang.String" not-null="true">
<column name="USERNAME" />
</property>
<property name="password" type="java.lang.String" not-null="true">
<column name="PASSWORD" />
</property>
<set name="orders" table="ORDERS" inverse="true" cascade="all" lazy="true">
<key>
<column name="EMPLOYEEID" />
</key>
<one-to-many class="database.entities.Order" />
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="database.entities.Customer" table="CUSTOMER">
<id name="customerId" type="java.lang.Long">
<column name="CUSTOMERID" />
<generator class="identity" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMERNAME" />
</property>
<set name="orders" table="ORDERS" inverse="true" cascade="all" lazy="true">
<key>
<column name="CUSTOMERID" />
</key>
<one-to-many class="database.entities.Order" />
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="database.entities.Order" table="ORDERS">
<id name="orderId" type="java.lang.Long">
<column name="ORDERID" />
<generator class="identity" />
</id>
<property name="orderDate" type="java.util.Date">
<column name="ORDERDATE" />
</property>
<property name="quantity" type="java.lang.Integer">
<column name="QUANTITY" />
</property>
<property name="quantityMargin" type="java.lang.Long">
<column name="QUANTITYMARGIN" />
</property>
<property name="port" type="java.lang.String">
<column name="PORT" />
</property>
<property name="orderState" type="java.lang.String">
<column name="ORDERSTATE" />
</property>
<many-to-one name="customer" class="database.entities.Customer" cascade="all" fetch="join">
<column name="CUSTOMERID" />
</many-to-one>
<many-to-one name="associate" column="EMPLOYEEID" class="database.entities.Employee" cascade="all" fetch="join">
</many-to-one>
</class>
</hibernate-mapping>
UDATE 2:
class Employee{
//Various members
Set<Order> orders = new HashSet<Order>();
public void addOrder(Order order){
order.setEmployee(this);
orders.add(order);
}
}
Also:
class Customer{
//Various members
Set<Order> orders = new HashSet<Order>();
public void addOrder(Order order){
order.setCustomer(this);
orders.add(order);
}
}

It seems to me that in the DBUtils code, you are wrapping both saves in an outer transaction, whereas in the Utils code, you have them in two completely separate transactions without an outer one.
Depending on your cascading, Hibernate has to figure out which objects need to be saved. When you run the Utils code, with two separate transactions, it will save the Order first. Since you're cascading all, this means it will save the Order first, then the Customer. However, you've created the SAME Order for both the Customer and Employee. Therefore, the Employee also gets saved in the first transaction (due to cascading). The second transaction, since it is separate, will not be aware of this and save another Employee.
On the other hand, if you wrap them in an outer transaction, Hibernate can figure out the identities of all the objects and save them properly.

try saving only Order instead of saving Employee and Customer separately. With your existing cascade=all setting both parent object will get saved without creating any duplicates.

Related

Hibernate Mapping - Join of two table to one flat class

I have two tables: patient_data and patient_diagnosis
Patient_data contains personal data of patient like: pid (pkey), gender, birth_date
patient_diagnosis contains the diagnosis data of the registered patients. It has fields like: record_id (pkey), pid (fkey to patient_data(pid)), diagnosis_date and other related fields.
Now, I want to join these two tables on pid and have all these fields in a single type of object.
Here is the mapping file:
<hibernate-mapping>
<class catalog="emr" name="in.Models.Emr" table="patient_diagnosis">
<id name="recordid" type="long">
<column name="record_id"/>
</id>
<property name="diagnosisDate" type="timestamp">
<column length="19" name="diagnosis_date" not-null="true"/>
</property>
<property name="snomedTermPrimary" type="long">
<column name="snomed_term_primary" not-null="true" />
</property>
<property name="snomedTermSecondary" type="string">
<column name="snomed_term_secondary" />
</property>
<property name="episodeNo" type="long">
<column name="episode_no" not-null="true" />
</property>
<property name="pid" type="long">
<column name="pid" not-null="true" />
</property>
<join table="patient_data">
<key column="pid"/>
<property name="gender" type="string">
<column name="gender" not-null="true"/>
</property>
<property name="birthDate" type="timestamp">
<column length="19" name="birth_date" not-null="true"/>
</property>
</join>
</class>
</hibernate-mapping>
But, the join applies on patient_diagnosis.record_id = patient_data.pid instead of patient_diagnosis.pid = patient_data.pid i.e. HQL applies on primary key of first table with mentioned column from second table.
Please provide the solution so that join can be applied on mentioned column from first with mentioned column from second table. Or is there another way out?
Please note that in case I didn't create classes for patient_data or patient_diagnosis. But, just Emr class having combination of fields of these tables is created.
Try giving foreign key
<id name="pid" type="java.lang.Long">
<column name="pid" />
<generator class="foreign">
<param name="property">patient_data</param>
</generator>
</id>
I am not sure but maybe this should work.
And
<one-to-one name="patient_data" class="in.Models.Emr"
cascade="save-update"></one-to-one>
Similarly in Join class
<one-to-one name="patient_diagnosis" class="in.Models.Emr"
cascade="save-update"></one-to-one>
I hope this helps you.

Hibernate lazy loading not working many-to-one XML

I'm trying to lazily load a collection in Hibernate. From what I've read this is the best practice way to do this:
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Hibernate.initialize(this.truckReviews);
session.getTransaction().commit();
session.close();
return this.truckReview;
However, when I run this unit test, the assertion fails:
Truck realTruck = Truck.getTruckByID(1);
TruckReview realFakeReview = new TruckReview();
realFakeReview.setTruck(realTruck);
realFakeReview.setUser(IntegrationTestResources.getTestUser());
realFakeReview.setReviewDate(new Date());
realFakeReview.setReviewStars(5);
realFakeReview.setReviewText("fake review");
realFakeReview.save();
assertTrue(realTruck.loadReviews().contains(realFakeReview));
realFakeReview.delete();
Needless to say, I'm a bit confused since other StackOverflow threads say this is the way to do it properly. I feel like I must be missing something very obvious but I'm still new to Hibernate and have no idea what.
Here's the mapping file for this class:
<hibernate-mapping>
<class name="edu.temple.tutrucks.Truck" table="truck" catalog="TUTrucks" optimistic-lock="version">
<id name="id" type="int">
<column name="id" />
<generator class="identity" />
</id>
<property name="truckName" type="string">
<column name="truck_name" not-null="true" />
</property>
<property name="latitude" type="double">
<column name="latitude" precision="22" scale="0" not-null="true" />
</property>
<property name="longitude" type="double">
<column name="longitude" precision="22" scale="0" not-null="true" />
</property>
<property name="openingTime" type="imm_time">
<column name="opening_time" not-null="false"></column>
</property>
<property name="closingTime" type="imm_time">
<column name="closing_time" not-null="false"></column>
</property>
<property name="avatar" type="string">
<column name="avatar"></column>
</property>
<list name="truckReviews" table="truck_review" inverse="true" lazy="true" fetch="select">
<key>
<column name="truck_id" />
</key>
<list-index column="review_stars" />
<one-to-many class="edu.temple.tutrucks.TruckReview" />
</list>
<list name="menus" table="menu" inverse="true" lazy="false" fetch="select">
<key>
<column name="truck_id" />
</key>
<list-index column="id" />
<one-to-many class="edu.temple.tutrucks.Menu" />
</list>
<set name="tags" table="tag_truck_map" inverse="true" lazy="true" fetch="select">
<key>
<column name="truck_id" />
</key>
<many-to-many column="tag_id" class="edu.temple.tutrucks.Tag"></many-to-many>
</set>
</class>
</hibernate-mapping>
Thanks in advance.
UPDATE: Still having issues, I've changed the loadReviews method to this
#Override
public Truck loadReviews() { // lazy loading needs to be fixed
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Truck retval = (Truck) session.get(Truck.class, this.getId());
Hibernate.initialize(retail.getTruckReviews());
session.getTransaction().commit();
session.close();
retval.truckReviews.size();
return retval;
}
And the test now looks like this
#Test
public void testloadReviews() {
Truck realTruck = Truck.getTruckByID(1, false, false);
TruckReview realFakeReview = new TruckReview();
realFakeReview.setTruck(realTruck);
realFakeReview.setUser(IntegrationTestResources.getTestUser());
realFakeReview.setReviewDate(new Date());
realFakeReview.setReviewStars(5);
realFakeReview.setReviewText("fake review");
realFakeReview.save();
assertTrue(realTruck.loadReviews().getTruckReviews().contains(realFakeReview));
realFakeReview.delete();
}
The problem is that you can't to use realTruck form one session to do Hibernate.initialize(this.truckReviews) using other session
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
// you need to reload persistent with
// realTruck = session.get(), realTruck = session.load() here
Hibernate.initialize(this.getTruckReviews());
You need to use a getter not a property, because of Hibernate need to have a proxy. A proxy is returned by a getter
Hibernate.initialize(this.getTruckReviews());

org.hibernate.exception.MappingException or ConstraintViolation

I am using hibernate 4.1.9. I have Users, and the Users have a list of Accounts, and Accounts have list of Transactions. Here is my hbm.xml
<?xml version="1.0"?>
<class name="User" table="users">
<id name="userId" column="userid">
<generator class="increment"/>
</id>
<property name="username" column="username" not-null="true"/>
<property name="password" column="password" not-null="true"/>
<property name="registerDate" type="timestamp" column="register_date"/>
<list name="accounts" table="accounts" inverse="true" cascade="all">
<key column="userid" not-null="true"/>
<index column="accountid"/>
<one-to-many class="com.joe.data.Account"/>
</list>
</class>
<class name="Account" table="accounts">
<id name="accountId" column="accountid">
<generator class="increment"/>
</id>
<property name="balance" type="big_decimal" column="balance"/>
<property name="lastModified" type="timestamp" column="last_modified"/>
<list name="txns" table="transactions" inverse="true" cascade="all">
<key column="accountId" not-null="true"/>
<index column="transactionId"/>
<one-to-many class="com.joe.data.Transaction"/>
</list>
<many-to-one name="userId" class="User" column="userid" not-null="true"
unique="true" cascade="all"/>
<many-to-one name="accountType" class="AccountType" column="account_type"
not-null="true" cascade="all" unique="true" />
</class>
<class name="Transaction" table="transactions">
<id name="transactionId" column="transactionid">
<generator class="increment"/>
</id>
<property name="description" column="description"/>
<property name="amount" type="big_decimal" column="amount"/>
<property name="dateAdded" column="date_added"/>
<property name="reoccuring" type="numeric_boolean" column="reoccuring"/>
<many-to-one name="category" class="Category" column="category"
not-null="true" cascade="all" unique="true" />
</class>
<class name="Category" table="categories">
<id name="categoryId" column="categoryid"/>
<property name="categoryName" column="categoryname" not-null="true"/>
</class>
<class name="AccountType" table="account_types">
<id name="accountType" column="account_type"/>
<property name="accountName" column="name"/>
</class>
If I leave inverse="true" on the list of accounts (in the User) I get the ConstraintViolationException because the userid is not getting put in the insert query. If I take inverse="true" off of the list of accounts, I get org.hibernate.MappingException: Repeated column in mapping for entity: com.joe.data.Account column: accountid (should be mapped with insert="false" update="false")
To clarify lowercase names are database columns names, camel case are class variable names. I know Transaction class isn't working quite right yet, but if I could get the Accounts to insert I could do the same thing to get the Transactions to insert.
Edit: I added the many-to-one on the Account class and now I am getting another exception where hibernate is complaining about missing a getter for userId in com.joe.data.Account
In order to get inverse="true" work, you need to define many-to-one for User in Account and for Account in Transaction class and mappings.

Hibernate saveOrUpdate() doesn't seem to cascade creation and saving

I'm new to Hibernate and am trying to save a "UserState" with a list of "WorkspaceState"s. UserState objects are keyed by a username that is set, the WorkspaceStates are set by UUID scheme. My issue is that if I have a UserState with one WorkspaceState in it, the UserState gets saved but the WorkspaceState does not.
Here are the Hibernate mapping
<hibernate-mapping>
<class name="UserState" table="USERSTATE">
<id name="owner" type="java.lang.String">
<column name="OWNER" />
<generator class="assigned" />
</id>
<list name="workspaces" inverse="false" cascade="all" table="WORKSPACESTATE" lazy="true">
<key>
<column name="UID" />
</key>
<list-index></list-index>
<one-to-many class="WorkspaceState" />
</list>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="WorkspaceState" table="WORKSPACESTATE">
<id name="uid" type="java.lang.String">
<column name="UID" />
<generator class="uuid" />
</id>
<property name="owner" type="java.lang.String">
<column name="OWNER" />
</property>
</class>
</hibernate-mapping>
I have a UserState object with one WorkspaceState in it. When I do a session.saveOrUpdate(userst), I see that hibernate has already removed the WorkspaceState from my userst object. Then the commit saves it to the DB without the workspacestate in it.
In truth the WorkspaceState themselves have lists, but I suspect whatever I'm doing wrong continues onward.
Thanks
Edit - how it's committed. HibernateUtil is as it appears in the standard hibernate document examples:
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String username = (String) session.merge(state);
transaction.commit();
} catch (HibernateException e) {
transaction.rollback();
e.printStackTrace();
return false;
} finally {
session.close();
}
you need to change cascade reference to save-update and remove inverse attribute from workspace list mapping

Java Hibernate Cascading Issue

---- Introductionary information and problem domain ----
Basicly I have 3 tables in a database: 'User', 'Item', 'ItemsPerUser'.
Table User:
username (PK);
password;
email
Table Item
name (PK)
Table ItemsPerUser
username (PK) (and FK);
item_name (PK) (and FK)
When I don't use cascading, I get an error: "Cannot add or update a child row: a foreign key constraint fails".
The mapping files are correct. I need some sort of cascading. It works when I add cascading in the set property of the many-to-many relationship to add non-existent data to User & Item, but it is overwriting data in ItemsPerUser. Whenever I save an object which contains one ore more items which was already entered in ItemsPerUser, it overwrites the row, even when the other part of the PK is not the same user. So basicly the previous user with that 'item' is overwritten by the new user with the same item.
It should always add a new row in the table ItemsPerUser if it is a new user, even with one or more item(s) whom is already entered by another User object.
---- Visual styled example ----
Assume I start with an empty database and I insert a new user Roger, who has two items: coffee and water. This is an example what happens (Hibernate handles this correct):
User ItemsPerUser Item
Roger Roger-coffee coffee
Roger-water water
Now when I insert a new user "Alfonzo" whom has the items coffee and soda, this happens:
User ItemsPerUser Item
Roger Alfonzo-coffee coffee
Alfonzo Roger-water water
Alfonzo-soda soda
---- Code example(s) ----
//Mapping for databag 'User' - !! NOTE: I have deleted the cascade rule in the XML
<hibernate-mapping>
<class name="databag.User" table="User" catalog="androiddb">
<id name="username" type="string">
<column name="username" length="45" />
<generator class="assigned" />
</id>
<property name="password" type="string">
<column name="password" length="45" not-null="true" />
</property>
<property name="email" type="string">
<column name="email" length="45" not-null="true" unique="true" />
</property>
<set name="items" inverse="false" table="itemsperuser">
<key>
<column name="username" length="45" not-null="true" />
</key>
<many-to-many entity-name="databag.Items">
<column name="item_name" length="45" not-null="true"/>
</many-to-many>
</set>
</class>
</hibernate-mapping>
//Mapping for Item
<hibernate-mapping>
<class name="databag.Item" table="item" catalog="androiddb">
<id name="name" type="string">
<column name="name" length="45" />
<generator class="assigned" />
</id>
<set name="users" inverse="false" table="itemsperuser">
<key>
<column name="item_naam" length="45" not-null="true" />
</key>
<many-to-many entity-name="databag.User">
<column name="username" length="45" not-null="true" />
</many-to-many>
</set>
</class>
</hibernate-mapping>
//Saving an object
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction trans = session.beginTransaction();
session.save((User)o);
trans.commit();
session.close();
Note: '(User)o' contains none, one or more items.
You can't set inverse="false" on both sides. Hibernate cannot persists both sets (users in Item and items in User) in the same table, unless one of them is called 'inverse', and can be savely ignored for writing. It will be filled up only when reading.
I'd set inverse="true" on the Item side.
Then you'd have to save items first, then adding them to a user, then saving the user.

Categories

Resources