Hibernate One To Many Problem - java

I am doing a proof of concept for one to many mapping with no success. My schema is as follows:
Student ---->Phone
class Student
public class Student implements java.io.Serializable
{
private Set<Phone> studentPhoneNumbers = new HashSet<Phone>();
// other setters and getters and constructors
}
class Phone
public class Phone implements java.io.Serializable
{
private Student student;
// other setters and getters and constructors
}
Student Mapping File:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 5, 2010 7:56:05 PM by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
<class name="com.BiddingSystem.domain.Student" table="STUDENT">
<id name="studentId" type="long">
<column name="STUDENTID" />
<generator class="native" />
</id>
<property name="studentName" type="java.lang.String">
<column name="STUDENTNAME" />
</property>
<set name="studentPhoneNumbers" table="PHONE" inverse="true" cascade="all">
<key>
<column name="STUDENTID" not-null="true" />
</key>
<one-to-many class="com.BiddingSystem.domain.Phone" />
</set>
</class>
</hibernate-mapping>
Phone Mapping File:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 5, 2010 7:56:05 PM by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
<class name="com.BiddingSystem.domain.Phone" table="PHONE">
<id name="phoneId" type="long">
<column name="PHONEID" />
<generator class="native" />
</id>
<property name="phoneType" type="java.lang.String">
<column name="PHONETYPE" />
</property>
<property name="phoneNumber" type="java.lang.String">
<column name="PHONENUMBER" />
</property>
<many-to-one name="student" class="com.BiddingSystem.domain.Student" not-null="true">
<column name="STUDENTID" not-null="true"/>
</many-to-one>
</class>
</hibernate-mapping>
But when I am doing this:
Session session = gileadHibernateUtil.getSessionFactory().openSession();
Transaction transaction = session.beginTransaction();
Set<Phone> phoneNumbers = new HashSet<Phone>();
phoneNumbers.add(new Phone("house","32354353"));
phoneNumbers.add(new Phone("mobile","9889343423"));
Student student = new Student("Eswar", phoneNumbers);
session.save(student);
transaction.commit();
session.close();
I am getting the following errors:
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value: com.BiddingSystem.domain.Phone.student
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:101)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:313)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:144)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
at
org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:677)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:669)
at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:425)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:362)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:338)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:476)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:354)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:144)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
at com.BiddingSystem.server.GreetingServiceImpl.greetServer(GreetingServiceImpl.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at net.sf.gilead.gwt.PersistentRemoteService.processCall(PersistentRemoteService.java:174)
... 21 more
Can someone help in setting the proper attributes for this
phone mapping xml file
<many-to-one name="student" class="com.BiddingSystem.domain.Student" not-null="true">
<column name="STUDENTID" not-null="true"/>
</many-to-one>
and Student mapping file
<set name="studentPhoneNumbers" table="PHONE" inverse="true" cascade="all">
<key>
<column name="STUDENTID" not-null="true" />
</key>
<one-to-many class="com.BiddingSystem.domain.Phone" />
</set>

You need to push the Student object into the Phone objects:
foreach (Phone phone : student.getStudentPhoneNumbers()) {
phone.setStudent(student);
}
A more typical piece of code would create the Student instance first, and then add the phone numbers to it. I've often implemented a method to help with this, e.g. in Student.java:
public void addPhoneNumber(Phone phone) {
phone.setStudent(this);
getStudentPhoneNumbers().add(phone);
}
public void addPhoneNumber(String type, String number) {
addPhoneNumber(new Phone(type, number));
}
So now you can say student.addPhoneNumber("home", "12354") and it will simply DTRT.

Since you are using Bi-Direction association with inverse=true option, the child objects will be persisted independently. The parent object doesn't take care about synchronization with child objects. So in this case, there will one insert query for parent object and as many insert query as the child objects. So, child object need to have reference of parent object at insert time. Also you have defined not-null=true at parent as well as child side.
When we use inverse=false, parent will be saved first, then child will be saved without reference of parent object and at last the parent will update its relationship with child using the update queries.
Hope this helps.

1). Phone number must have a student so create a student object (stu = new Student()) first and
then call phone.setStudent(stu);
ex:
Set<Phone> phoneNumbers = new HashSet<Phone>();
Phone p1 = new Phone("house", "32354353");
phoneNumbers.add(p1);
Phone p2 = new Phone("mobile", "9889343423");
phoneNumbers.add(p2);
Student student = new Student("Eswar", phoneNumbers);
student.setPhones(phoneNumbers);
session.save(student); //Till now, This code is work if you use cascade = 'all' in //my_mapping_file.xml or
session.save(p1); session.save(p2); //call session.save on both phone objects

Related

Hibernate one-to-one mapping does not generate one-to-one diagram on MySQLWorkbench?

I am following a course on Hibernate. I have the following entity classes:
User
package com.example.model;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
#Data
public class User
{
private int id;
private String name;
private ProteinData proteinData;
private List<UserHistory> history = new ArrayList<UserHistory>();
public User() {
setProteinData(new ProteinData());
}
public void setProteinData(ProteinData proteinData) {
this.proteinData = proteinData;
proteinData.setUser(this);
}
public void addHistory(UserHistory historyItem) {
historyItem.setUser(this);
history.add(historyItem);
}
}
ProteinData
package com.example.model;
import lombok.Data;
#Data
public class ProteinData
{
private int id;
private User user;
private int total;
private int goal;
}
Mapping is done using XML and as follows:
User.hbm.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.example.model.User" table="USER">
<id name="id" type="int" column="ID">
<generator class="identity" />
</id>
<property name="name" type="string" column="NAME" />
<one-to-one name="proteinData" class="com.example.model.ProteinData" cascade="save-update" />
<list name="history" table="USER_HISTORY" inverse="true" cascade="save-update">
<key column="USER_ID" />
<list-index column="POSITION" />
<one-to-many class="com.example.model.UserHistory" />
</list>
</class>
</hibernate-mapping>
ProteinData.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.ugultopu.model.ProteinData" table="PROTEINDATA">
<id name="id" type="int">
<column name="ID" />
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<one-to-one name="user" class="com.ugultopu.model.User" constrained="true" />
<property name="total" type="int">
<column name="TOTAL" />
</property>
<property name="goal" type="int">
<column name="GOAL" />
</property>
</class>
</hibernate-mapping>
When I run the application, it works correctly. However, even though the relation between User and ProteinData is specified as one-to-one, when I generate a model diagram on MySQLWorkbench using the schema generated by Hibernate, the relation between User and ProteinData is depicted as one-to-many:
What might be the reason for this?
UPDATE
This is the model diagram after making the following changes as specified in Naros' answer:
<!-- User.hbm.xml -->
<one-to-one name="proteinData" property-ref="user" ... />
<!-- ProteinData.hbm.xml -->
<many-to-one name="user" unique="true" not-null=true" ... />
Most likely because there is no unique constraint defined on the User or ProteinData tables for the relationship between the two entities, therefore MySQLWorkbench believes its one-to-many.
One approach would be to specify the unique-constraint manually in the HBM mapping:
<hibernate-mapping>
...
<database-object>
<create>ALTER TABLE t ADD CONSTRAINT c UNIQUE (..)</create>
<drop>ALTER TABLE t DROP CONSTRAINT c</drop>
</database-object>
</hibernate-mapping>
Another approach would be to add unique constraints at the table level if you use JPA orm.xml
<entity class="User">
<table>
<unique-constraint>
<column-name>proteinData_id</column-name>
</unique-constraint>
</table>
...
</entity>
<entity class="ProteinData">
<table>
<unique-constraint>
<column-name>user_id</column-name>
</unique-constraint>
</table>
...
</entity>
Or specify this as part of an annotated class.

Lazy loading with formula doesn't working

I'm looking for retrieve a calculated field from an entity.
I have a Customer entity and I want to know the total orders ordered by the customer but I need a lazy loading fetch, so I expect that "totalOrders" field will be calculated when Customer.getTotalOrders() it's called.
With the follow configuration the lazy loading doesn't work and totalOrders always is being calculated.
What I'm doing wrong?
XML Mapping on Customer entity:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.tumrapp.entities.Customer" table="customers">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<property name="name" type="string">
<column name="name" not-null="true" />
</property>
<property update="false" insert="false" name="totalOrders" type="int" lazy="true"
formula="(select count(o.id) from orders o where o.customer_id = id)">
</property>
</class>
</hibernate-mapping>
Customer entity class:
public class Customer implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer totalOrders;
//constructors
//simple getters and setters
}
Query displayed on Hibernate console:
select
customer0_.id as id61_0_,
customer0_.name as name61_0_,
(select
count(o.id)
from
orders o
where
o.customer_id = customero0_.id) as formula1_0_
from
customer customer0_
where
customer0_.id=?
Formulas will always be evaluated on the initial select and the lazy property will be silently ignored unless you add bytecode instrumentation (example via Ant / Maven) for the entity class. The same holds true for one-to-one joins.

Strange behavior on one-to-one and many-to-one (unique=true) fetching in hibernate

I'm trying to achieve lazy load on the following.
User.java
public class User {
private int id;
private String userName;
private String password;
private Employee employee;
//getter and setters
}
User.hbm.xml
<hibernate-mapping>
<class name="com.site.dto.User" table="user">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<property name="userName" type="string" update="false">
<column name="user_name" length="50" not-null="true" unique="true" />
</property>
<property name="password" type="string">
<column name="password" length="50" not-null="true" unique="true" />
</property>
<one-to-one name="employee" class="com.site.dto.Employee" fetch="select" cascade="save-update" />
</class>
</hibernate-mapping>
Employee.java
public class Employee implements Serializable{
private int id;
private String name;
private String email;
private User user;
// getter and setters
}
Employee.hbm.xml
<hibernate-mapping>
<class name="com.site.dto.Employee" table="employee">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<property name="name" type="string">
<column name="name" length="50" not-null="true" unique="true" />
</property>
<property name="email" type="string" update="false">
<column name="email" length="50" not-null="true" unique="true" />
</property>
// enforcing many-to-one to one-to-one by putting unique="true"
<many-to-one name="user" column="user_id" class="com.site.dto.User" unique="true" not-null="true" fetch="select" cascade="save-update" />
</class>
</hibernate-mapping>
First I'm getting the User Object based on username. Now I'm trying to load the employee object which gives me null pointer exception. So after digging on some debug, it seems to be using a select statement with wrong where clause. Here is the hibernate debug
select employee0_.id as id1_1_0_, employee0_.name as name2_1_0_, employee0_.email as email3_1_0_,employee0_.user_id as user_id25_1_0_, from employee employee0_ where employee0_.id=?
Why is the where clause is based on employee.id and not employee.user.id ? I think this is due to the reason on how one-to-one mapping works in hbm.xml configuration where one-to-one will be linked to child table's primary key id but not user_id. I'm forcing the many-to-one to one-to-one in employee by using unique="true". I can fetch the employee in Hibernate annotation's one-to-one by defining #Join-column but I can't figure out how to map the one-to-one in hbm.xml which should refer the child's user_id.
Figured out the solution a while back, but forget to post it.
The above problem is coz, by default one-to-one mapping will be implemented for a child table which have the parent's primary key as the Child's primary key. So if we're going to eliminate that default property and use one-to-one with many-to-one (unique=true), we should define property-ref
I've added property-ref in one-to-one mapping in User.hbm.xml and now it works fine.
<one-to-one name="employee" property-ref="user" class="com.site.dto.Employee" fetch="select" cascade="save-update" />

What is wrong with this code that saves value objects?

I have two entities and two value objects - Employee, Card, Employee Number & Card Number. The relationship between Employee and Card is a one-to-many. I create an instance of Employee and an instance of Card like so and save them to the database...
EmployeeRepositoryHibernate employeeRepository = new EmployeeRepositoryHibernate();
employeeRepository.setSessionFactory();
employeeRepository.getSession().beginTransaction();
EmployeeNumber employeeNumber = new EmployeeNumber("MNO");
Location location = new Location("Room 1");
CardNumber cardNumber = new CardNumber("1");
Employee employee = new Employee(employeeNumber, location);
Card card = new Card(cardNumber, "1111", employee);
employeeRepository.getSession().save(employee);
employeeRepository.getSession().save(card);
employeeRepository.getSession().getTransaction().commit();
employeeRepository.getSession().close();
Except, it won't save, the following error message is shown... I can save an employee, but the message is thrown when I try to save a related card... the mysql database isn't relational yet.. both tables are separate...
Problem fixed: required related tables.
Caused by: java.sql.SQLException: Field 'employeeNumber' doesn't have a default value
Here are the two Hibernate XML mapping files for Card and Employee...
Card
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field">
<class name="model.Card" table="Card">
<id name="CardID" type="long">
<column name="CardID" />
<generator class="identity" />
</id>
<component name="cardNumber" unique="true">
<property name="number" column="cardNumber"/>
</component>
<many-to-one name="employee" class="model.Employee" fetch="select">
<column name="EmpID" not-null="true"></column>
</many-to-one>
<property name="PIN" column="PIN"/>
</class>
</hibernate-mapping>
Employee
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field">
<class name="model.Employee" table="employee">
<id name="EmpID" column="EmpID">
<generator class="org.hibernate.id.IdentityGenerator"/>
</id>
<component name="employeeNumber" class="model.EmployeeNumber" >
<property name="number" column="employeeNumber" type="string"/>
</component>
<component name="location">
<property name="location" column="Location" type="string"/>
</component>
<set name="cards" inverse="true" cascade="all">
<key>
<column name="EmpID" not-null="true"></column>
</key>
<one-to-many class="model.Card"/>
</set>
</class>
</hibernate-mapping>
One of these might help:
Add a default value to the column employeeNumber
ALTER TABLE 'table_name' ALTER 'employeeNumber' SET DEFAULT NULL
Use auto increment if you are using employeeNumber as a primary key.
Supply value to the employeeNumber column during insertion.

Hibernate one to many relationship does not provide value for foreign key

Its being a while since I don't do Hibernate and I wanted to do some simple example the other day but when I needed to do a one to many relationship the many side doesn't get inserted into the database. This is how the database looked like.
This are my mappings for the person:
Java
public class ORMPerson implements Serializable {
private Long uniqueId;
private String firstName;
private String secondName;
private Long fkAddress;
hbm
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="orm">
<class name="orm.ORMPerson" table="PERSON">
<id name="uniqueId" column="UNIQUE_ID">
<generator class="increment"/>
</id>
<property name="firstName" column="FIRST_NAME"/>
<property name="secondName" column="SECOND_NAME"/>
<many-to-one name="fkAddress" class="orm.ORMPerson" column="FK_ADDRESS" cascade="all" not-null="false" />
</class>
</hibernate-mapping>
This are the mappings for Address:
Java
public class ORMAddress implements Serializable {
private Long uniqueId;
private String firstLine;
private String secondLine;
private String postcode;
private Set<ORMPerson> ormPersons;
hbm
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="orm">
<class name="orm.ORMAddress" table="ADDRESS">
<id name="uniqueId" column="UNIQUE_ID">
<generator class="increment"/>
</id>
<property name="firstLine" column="FIRST_LINE"/>
<property name="secondLine" column="SECOND_LINE"/>
<property name="postcode" column="POSTCODE"/>
<set name="ormPersons" table="ADDRESS" inverse="true" fetch="select" cascade="save-update">
<key>
<column name="UNIQUE_ID" not-null="true" />
</key>
<one-to-many class="ORMPerson"/>
</set>
</class>
</hibernate-mapping>
And this is the client code that I use to insert an address with multiple persons:
ORMAddress ormAddress = new ORMAddress();
ormAddress.setFirstLine(address.getFirstLine());
ormAddress.setSecondLine(address.getSecondLine());
ormAddress.setPostcode(address.getPostcode());
ormAddress.setOrmPersons(ormPersons);
session.save(ormAddress);
session.getTransaction().commit();
If I try to call the session.save() method with ormPersons, I will see the data being added to the database, but the foreign kew will have no value. I think this is because I just have a not-null="false" in Person but this is not a solution, I think all should be inserted automatically by just calling once the save method.
The reason is hidden in the inverse="true" mapping. This is saying to Hibernate:
when you persist collection - let that job on its items. These items must be aware of their parent.
But as we can see above, the ormPersons are not provided with back reference to ormAddress.
...
// after that line we have to do more
ormAddress.setOrmPersons(ormPersons);
// we have to assign back reference
for(ORMPerson ormPerson: ormPersons) {
ormPerson.setOrmAddress(ormAddress);
}
and also we would need ORMAddress reference inside of the ORMPerson - not as LONG
public class ORMPerson implements Serializable {
...
private ORMAddress ormAddress;
hbm
<class name="orm.ORMPerson" table="PERSON">
....
<many-to-one name="ormAddress" class="orm.ORMPerson"
column="FK_ADDRESS" cascade="all" not-null="false" />
</class>
And finally, many-to-one and one-to-many must use the same column
hbm of Address (FK_ADDRESS):
<class name="orm.ORMAddress" table="ADDRESS">
...
<set name="ormPersons" table="ADDRESS" inverse="true"
fetch="select" cascade="save-update">
<key>
//<column name="UNIQUE_ID" not-null="true" />
<column name="FK_ADDRESS" not-null="true" /> // the parent id
</key>
<one-to-many class="ORMPerson"/>
</set>
Check the doc for an example:
23.2. Bidirectional one-to-many

Categories