Diving deeper into EJB 3.0 Persistence with GlassFish (RI) – still out of container

0

In an earlier post, we embarked on a tour of EJB 3.0 Persistence for J2SE (stand alone out-of-container) applications: Getting Started with EJB 3.0 Persistence out-of-container using the Reference Implementation (GlassFish).
In that post we developed a simple application – in fact the simplest
application – that persisted Departments to a backend database and
retrieved both Department and Employee objects from that database. The
example was based on the sample schema in every Oracle database with
tables EMP and DEPT- but could have used any database. The article did
not go beyond EJB 3.0 maping annotations for Entity (identify POJO as a
persisted entity) and Entity to Table:

@Entity<br />@Table(name=&quot;EMP&quot;)&nbsp;<br />

and for Property to Column mapping and for Primary Key indicator:

@Id<br />@Column(name=&quot;EMPNO&quot;, nullable=false) <br />

Obviously,
there is much more to mapping Entities to backend database objects. In
this article we will go a little bit further in exploring additional
mapping meta-data and how they are interpreted. I am particularly
interested in relationships – foreign keys, master detail, many to
many, lazy loading vs. loading the entire object graph etc.....

In
our example – EMP and DEPT – there are two foreign key relations:
Employees belong to a Department through their DEPTNO column that
references the DEPTNO primary key column in the DEPT table.
Furthermore, Employees can have a Manager, a foreign key from Employee
to Employee, from the column MGR to the primary key column EMPNO. Of
course, you can say this in reverse as well: an Employee can be a
manager of a group of suborindates. And a Department may have a staff
of several employees. So in Object terms, a Department object could
have a collection of Employee instances – its staff – whereas the
Employee could have a collection of subordinate Employees.

ManyToOne – Foreign Reference

The
simplest type of dependency is the (outgoing) reference, a single
referenced record. In our sample every Employee references a
Department. To have the EJB 3.0 EntityManager deal with this, we do the
following:

Add the Department property or attribute to class
Employee in Employee.java. Specify the fact that this attribute is
based on a many to one (outgoing) reference, using the annotation
@ManyToOne. The type of this attribute – Department – combined with the
mapping information for class Department (@Entity @Table(name="DEPT")
public class Department implements Serializable ) determines the
referenced table – which is DEPT. The @JoinColumn annotation is used to
specify which column in the table mapped to the Employee entity
implements the reference – to the primary key column of the referenced
type.

in class Employee:

add imports:

import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;

in the body of the class: 

… 

&nbsp;&nbsp;&nbsp; @ManyToOne(optional=false)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @JoinColumn(name=&quot;deptno&quot;)<br />&nbsp;&nbsp;&nbsp; public Department getDepartment() {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return department; <br />&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp; &nbsp; public void setDepartment(Department department) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.department = department;<br />&nbsp;&nbsp;&nbsp; }

in our HrmClient code:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HrmService hrmService = new HrmService();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // use the predefined service method for finding all employees<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // and printing all their names<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List&lt;Employee&gt;&nbsp; emps = hrmService.findAllEmp();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (Employee e : emps) { <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e.getEname()+&quot; in Department &quot;+e.getDepartment().getDname());<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />

with the following results:

[TopLink Info]: 2005.12.31 07:49:48.273–ServerSession(22543186)–file:/C:/glassfish/ejb30_se/se1/classes-pu1 login successful
SMITH in Department RESEARCH
ALLEN in Department SALES
WARD in Department SALES
JONES in Department RESEARCH
MARTIN in Department SALES
BLAKE in Department SALES
CLARK in Department ACCOUNTING
SCOTT in Department RESEARCH

The ManyToOne annotation can take a number of properties:

  String targetEntity() default "";  – is taken from the type of the annotated attribute if none is provided
  CascadeType[] cascade() default {}; – is one of ALL, PERSIST, MERGE, REMOVE, REFRESH
  FetchType fetch() default EAGER; – is LAZY or EAGER: should the
referenced object be instantiated when the referencing object is
instantiated or objects referenced across relationships can be loaded
on an as needed basis
  boolean optional() default true;

Reference: ManyToOne

The JoinColumn annotation also takes a number of properties:

  String name() default "";
  String referencedColumnName() default "";
  boolean primaryKey() default false;
  boolean unique() default false;
  boolean nullable() default true;
  boolean insertable() default true;
  boolean updatable() default true;
  String columnDefinition() default "";
  String secondaryTable() default ""; 

Reference: JoinColumn 

Eager vs. Lazy Loading

By
adding the property fetch to the ManyToOne annotation, we can control
the eager or lazy loading of the referenced object – the default is
eager. We easily see the difference in behavior:

I add the line

    public Department() {
      System.out.println("Department instantiated");
    }

to
the default constructor in Department. Thus I will see in the output
when a Department object is created. When I next run the HrmClient code
- with default setting for Fetch of EAGER, the output consists of 14
times DEPT instantiated, followed by the 14 employees and their
department names. If I change the the ManyToOne annotation to:

    @ManyToOne(optional=false, fetch=FetchType.LAZY)
      @JoinColumn(name="deptno")
    public Department getDepartment() {
       return department;
    }

and run the HrmClient again, the output changes to:

[TopLink
Info]: 2005.12.31 08:27:32.319–ServerSession(2859291)–TopLink,
version: Oracle TopLink Essentials – 10g release 4 (10.1.4.0.0) (Build
051215Dev)
DEPT instantiated
[TopLink Info]: 2005.12.31 08:27:34.252–ServerSession(2859291)–file:/C:/glassfish/ejb30_se/se1/classes-pu1 login successful
DEPT instantiated
DEPT instantiated
DEPT instantiated
SMITH in Department RESEARCH
DEPT instantiated
DEPT instantiated
DEPT instantiated
ALLEN in Department SALES
WARD in Department SALES
JONES in Department RESEARCH
MARTIN in Department SALES
BLAKE in Department SALES
DEPT instantiated
DEPT instantiated
DEPT instantiated
CLARK in Department ACCOUNTING
SCOTT in Department RESEARCH
KING in Department ACCOUNTING
TURNER in Department SALES
ADAMS in Department RESEARCH
JAMES in Department SALES
FORD in Department RESEARCH
MILLER in Department ACCOUNTING
Process exited with exit code 0.

To
be honest, I do no completely understand this output. What is clear is
that not all Departments are instantiated at the same time, and
definitely not eagerly at the same time of selecting all Employees.
However, and it only strikes me now that it also happened in the
previous case with eager loading: I see 10 Department instantiations.
There are only three distinct Departments – so three instantiations
should have been enough. However, there are 14 employee. So if we do
not have the three instantiations, then why not 14 but 10 instead?

Perhaps that turning up the logging volume – set property toplink.logging.level=FINE
in the file persistence.xml – will give some insight. Well, it does: we
can see that only three select statements are executed for the
departments, one for each of the distinct departments. However, we
still see 10 instantiations. It does seem though that: there are three
instantiations for each Department involved and there is one ‘bonus’
instantiation, not related to a specific department at all:

 

Department instantiated[TopLink Info]: 2006.01.01 06:36:16.470--ServerSession(20727434)--Thread(Thread[main,5,main])--file:/C:/glassfish/ejb30_se/se1/classes-pu1 login successful<br />[TopLink Fine]: 2006.01.01 06:36:16.740--ServerSession(20727434)--Connection(32048085)--Thread(Thread[main,5,main])--SELECT EMPNO, HIREDATE, DEPTNO, JOB, MGR, COMM, SAL, ENAME, deptno FROM EMP<br />[TopLink Fine]: 2006.01.01 06:36:16.930--ServerSession(20727434)--Connection(15696851)--Thread(Thread[main,5,main])--SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE (DEPTNO = 20)<br />Department instantiated deptno=20<br />Department instantiated deptno=20<br />Department instantiated deptno=20<br />SMITH(7369) in Department RESEARCH<br />[TopLink Fine]: 2006.01.01 06:36:16.930--ServerSession(20727434)--Connection(32048085)--Thread(Thread[main,5,main])--SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE (DEPTNO = 30)<br />Department instantiated deptno=30<br />Department instantiated deptno=30<br />Department instantiated deptno=30<br />ALLEN(7499) in Department SALES<br />WARD(7521) in Department SALES<br />JONES(7566) in Department RESEARCH<br />MARTIN(7654) in Department SALES<br />BLAKE(7698) in Department SALES<br />[TopLink Fine]: 2006.01.01 06:36:16.940--ServerSession(20727434)--Connection(15696851)--Thread(Thread[main,5,main])--SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE (DEPTNO = 10)<br />Department instantiated deptno=10<br />Department instantiated deptno=10<br />Department instantiated deptno=10<br />CLARK(7782) in Department ACCOUNTING<br />SCOTT(7788) in Department RESEARCH<br />KING(7839) in Department ACCOUNTING<br />TURNER(7844) in Department SALES<br />ADAMS(7876) in Department RESEARCH<br />JAMES(7900) in Department SALES<br />FORD(7902) in Department RESEARCH<br />MILLER(7934) in Department ACCOUNTING<br /><br />

Let’s
test for object equality – Employee BLAKE and Employee ALLEN are both
in Departments SALES. So I would like the test
blake.getDepartment()==allen.getDepartment() to hold true – there
should only be a single object for Department SALES.

<font face="courier new,courier,monospace">public class HrmClient {<br />&nbsp;&nbsp;&nbsp; public static void main(String[] args) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HrmService hrmService = new HrmService();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee allen = hrmService.findEmployee(new Long(7499));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee blake = hrmService.findEmployee(new Long(7698));<br />&nbsp;       System.out.println(&quot;Departments for Allen:&quot;+allen.getDepartment().getDname()<br />                          +&quot; and Blake&quot;+blake.getDepartment().getDname());<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Are the Departments equal: &quot;+(allen.getDepartment()==blake.getDepartment()?&quot;Equal&quot;:&quot;Not equal&quot;));<br /></font>...

Note: this code relies on the method findEmployee in HrmService:

&nbsp;&nbsp;&nbsp; public Employee findEmployee(Long empno) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return getEntityManager().find(Employee.class, empno);<br />&nbsp;&nbsp;&nbsp; }

The output of this code:

Department
instantiated[TopLink Info]: 2006.01.01
06:40:14.272–ServerSession(20727434)–Thread(Thread[main,5,main])–file:/C:/glassfish/ejb30_se/se1/classes-pu1
login successful
[TopLink Fine]: 2006.01.01
06:40:14.372–ServerSession(20727434)–Connection(32048085)–Thread(Thread[main,5,main])–SELECT
EMPNO, HIREDATE, DEPTNO, JOB, MGR, COMM, SAL, ENAME, deptno FROM EMP
WHERE (EMPNO = 7499)
[TopLink Fine]: 2006.01.01
06:40:14.542–ServerSession(20727434)–Connection(15696851)–Thread(Thread[main,5,main])–SELECT
EMPNO, HIREDATE, DEPTNO, JOB, MGR, COMM, SAL, ENAME, deptno FROM EMP
WHERE (EMPNO = 7698)
[TopLink Fine]: 2006.01.01
06:40:14.552–ServerSession(20727434)–Connection(32048085)–Thread(Thread[main,5,main])–SELECT
DEPTNO, DNAME, LOC FROM DEPT WHERE (DEPTNO = 30)
Department instantiated deptno=30
Department instantiated deptno=30
Department instantiated deptno=30
Department instantiated deptno=30
Department instantiated deptno=30
Departments for Allen: SALES and Blake SALES
Are the Departments equal: Not equal

The
last line is the essential one: even though Allen and Blake are in the
same department, and both are instantiated by the same EntityManager
instance, they have references to different Department objects! That is
really not ideal. Having a database background myself, I am used to
working with different records instances that represent the same
underlying table record However, one of the benefits of the World of
Objects as I understand them is a much more natural fit to the world we
live in. We deal with objects that have properties and behavior that
resemble what we or our business do. It is not geared towards database
technology. These are some of the promises. EJB 3.0 Persistence clearly
has no time for such lofty ideas – you want an object? Here, you have
(a reference to) one. Want another one? Here, is another (reference to
another) object. Bummer.

So what we need is an advanced
EntityManager implementation that contains a cache that checks whether
an object with the same identity has already been handed out and if so
hands out a reference to that same object – rather than instantiating a
new object with the same values based on the same database record. I
suppose that is what EJB 3.0 Persistence implementations like TopLink
and Hibernate will add to the base Reference Implementation in
GlassFish. Note: there must be a very rudimentary sort of cache to
prevent duplicate queries for Department 30 in the example above.
Fortunately the RI is open source – time to take a peek! (see a later
post)

OneToMany – incoming reference or associated collection

The
other end of the relationship does not have a singular reference, it is
associated with a set of objects that all reference it. A Department in
our case can have a set of Employees (its Staff) associated with it.
Note: the association in the database is from the Employee side only.
However, in our Class Model we have a two-way street: Employee objects
may reference Department objects, a Department object can have a
Collection of referencing Employee objects. When the Department is
retrieved by the EntityManager, it will have a collection of Employee
objects assigned to it. Just like Employee objects will get associated
Department objects.

We change the code for the Department class:

<font face="courier new,courier,monospace">import javax.persistence.OneToMany;<br />import javax.persistence.CascadeType;<br />... <br />&nbsp;&nbsp;&nbsp; private Collection&lt;Employee&gt; staff; <br />&nbsp;&nbsp;&nbsp; @OneToMany(mappedBy=&quot;department&quot;, cascade={CascadeType.ALL})<br />&nbsp;&nbsp;&nbsp; public Collection&lt;Employee&gt; getStaff() {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return staff;<br />&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp; public void setStaff(Collection staff) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.staff = staff;<br />&nbsp;&nbsp;&nbsp; }</font><br />

The
mapping is taken care of through the type Employee associated with the
staff Collection, the property mappedBy that refers to the referencing
property in the Employee class and the joinColumn(name=Deptno)
annotation for the department attribute in Employee. Notice that the
mappedBy attribute refers to the property name of the
owning reference, in this case "department" The type of the related
class is determined through the use of generics. In order to keep both
sides of the relationship consistent with
each other it is common to assign the owning side the responsibility of
keeping the relationship consistent. For example, you could have the
setDepartment method on Employee have add the Employee to the staff
collection of the Department involved so it would maintain both ends of
the relationship.

I add a findDepartment method to the HrmService class:

    public Department findDepartment(Long deptno) {
        return getEntityManager().find(Department.class, deptno);
    }

and write the following code in HrmClient:

public class HrmClient {<br />&nbsp;&nbsp;&nbsp; public static void main(String[] args) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HrmService hrmService = new HrmService();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Department sales = hrmService.findDepartment(new Long(30));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Sales &quot;+sales.getDname()+&quot; staff:&quot;+sales.getStaff());<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Sales &quot;+sales.getDname()+&quot; staffsize:&quot;+sales.getStaff().size());<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee allen = hrmService.findEmployee(new Long(7499));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee blake = hrmService.findEmployee( new Long(7698));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Departments for Allen: &quot;+allen.getDepartment().getDname()+&quot; and Blake &quot;+blake.getDepartment().getDname());<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Are the Departments equal: &quot;+(allen.getDepartment()==blake.getDepartment()?&quot;Equal&quot;:&quot;Not equal&quot;));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Is the Allen object in the Sales Staff collection? &quot;+(sales.getStaff().contains(allen)?&quot;Yes&quot;:&quot;No&quot;));<br />&nbsp;&nbsp; }<br />}

The results are as follows:

DEPT instantiated
[TopLink
Info]: 2005.12.31
10:30:57.809–ServerSession(20727434)–Thread(Thread[main,5,main])–file:/C:/glassfish/ejb30_se/se1/classes-pu1
login successful
[TopLink Fine]: 2005.12.31
10:30:57.879–ServerSession(20727434)–Connection(32048085)–Thread(Thread[main,5,main])–SELECT
DEPTNO, DNAME, LOC FROM DEPT WHERE (DEPTNO = 30)
DEPT instantiated
DEPT instantiated
DEPT instantiated
Sales SALES staff:{IndirectList: not instantiated}
[TopLink
Fine]: 2005.12.31
10:30:58.050–ServerSession(20727434)–Connection(15696851)–Thread(Thread[main,5,main])–SELECT
EMPNO, HIREDATE, DEPTNO, JOB, MGR, COMM, SAL, ENAME, deptno FROM EMP
WHERE (deptno = 30)
Sales SALES staffsize:6
DEPT instantiated
DEPT instantiated
DEPT instantiated
DEPT instantiated
Departments for Allen: SALES and Blake SALES
Are the Departments equal: Not equal
Is the Allen object in the Sales Staff collection? No

The
number of Department objects instantiated does not change noticeably.
The number of select statements for Employee objects has changed
through: instead of retrieving ALLEN and BLAKE individually in two
separate SELECT statements, they are now retrieved as part of the Staff
collection of Department 30. And apparently that is somehow remembered
by the EntityManager. However, the Allen and Blake objects that result
from the findEmployee calls are NOT the same objects as the Employee
objects for Allen and Blake in the staff Collection of Department 30. I
find this bewildering. Unless the logging is fooling me, Allen and
Blake are queried only once but instantiated twice, once in the Staff
collection and once as result of the findEmployee call.  Both
instantiations derive from the same JDBC result. Once again: time to
look in that source code!

From retrieval to manipulation

Before
we saw how to specify relationships between entities. We have seen the
two most common typesL ManyToOne and OneToMany – these appear almost
always as a couple by the way. EJB 3.0 Persistence also has a OneToOne
and a ManyToMany type relation. More on those in a later post. Let’s
now take a look at manipulation of objects with relations.

The
following code creates and persists a new Department Toys and its staff
Tobias. Note that although I only explicitly persist the Department,
the Employee is implicitly persisted as well:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EntityTransaction tx = hrmService.getEntityManager().getTransaction();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.begin();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee tobias = new Employee();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEname(&quot;Tobias&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEmpno(new Long(2000));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Department toys = new Department();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDeptno(new Long(60));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDname(&quot;Toys&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setLocation(&quot;Zoetermeer&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Collection&lt;Employee&gt; staff = new ArrayList&lt;Employee&gt;();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; staff.add(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setStaff(staff);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().persist(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Commit the transaction<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();<br />

The logging:

Department instantiatedDepartment instantiated[TopLink Fine]: 2006.01.01 07:05:25.234--UnitOfWork(5569009)--Connection(7792807)--Thread(Thread[main,5,main])--INSERT INTO DEPT (DEPTNO, DNAME, LOC) VALUES (60, 'Toys', 'Zoetermeer')<br />[TopLink Fine]: 2006.01.01 07:05:25.274--UnitOfWork(5569009)--Connection(7792807)--Thread(Thread[main,5,main])--INSERT INTO EMP (EMPNO, HIREDATE, JOB, MGR, COMM, SAL, ENAME, deptno) VALUES (2000, NULL, NULL, NULL, NULL, NULL, 'Tobias', NULL)<br /><br />

Note:
the DEPTNO column for the new EMP record has NOT been set to 60! So
even though the EM knows that Tobias is part of the staff for
Department 60, and although it uses that knowledge to insert EMP along
with inserting the new DEPT, it still does not set the DEPTNO column.
So I need to explicitly set the Department on the Employee
(tobias.setDepartment(toys)) in addition to adding tobias to the staff. 

Now I am wondering what happens if I try to persist the Employee explicitly. Will there be a double insert of Tobias?:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // inside a transaction, create a new employee - persist it - and a new department - and persist it!&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EntityTransaction tx = hrmService.getEntityManager().getTransaction();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.begin();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee tobias = new Employee();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEname(&quot;Tobias&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEmpno(new Long(2000));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().persist(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Department toys = new Department();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDeptno(new Long(60));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDname(&quot;Toys&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setLocation(&quot;Zoetermeer&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Collection&lt;Employee&gt; staff = new ArrayList&lt;Employee&gt;();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; staff.add(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setStaff(staff);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().persist(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Commit the transaction<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();<br />

The
results are exactly the same. The same Insert statements are executed -
in the same order! There is no guarantee that the EntityManager will
perform the database operations at the time and even in the order as we
specify them. To enforce the immediate execution of DML statements in
the database, we have to flush the entity manager:

With the
explicit flush (hrmService.getEntityManager().flush();) we get the
expected sequence of Insert statements. There is no implicit insert of
Employee Tobias when we persist the new Department: the EntityManager
apparently realizes that it has already inserted that employee. Still,
the DEPTNO column of the new EMP record is NULL. Only when we
explicitly set the Department attribute of the Employee Entity will we
have that value set:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EntityTransaction tx = hrmService.getEntityManager().getTransaction();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.begin();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee tobias = new Employee();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEname(&quot;Tobias&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEmpno(new Long(2000));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().persist(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().flush();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Department toys = new Department();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDeptno(new Long(60));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDname(&quot;Toys&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setLocation(&quot;Zoetermeer&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Collection&lt;Employee&gt; staff = new ArrayList&lt;Employee&gt;();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; staff.add(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setStaff(staff);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setDepartment(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().persist(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Commit the transaction<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit()

And the logging:

Department instantiated[TopLink Info]: 2006.01.01 07:28:07.393--ServerSession(20727434)--Thread(Thread[main,5,main])--file:/C:/glassfish/ejb30_se/se1/classes-pu1 login successful<br />[TopLink Fine]: 2006.01.01 07:28:07.483--UnitOfWork(4558657)--Connection(10769718)--Thread(Thread[main,5,main])--INSERT INTO EMP (EMPNO, HIREDATE, JOB, MGR, COMM, SAL, ENAME, deptno) VALUES (2000, NULL, NULL, NULL, NULL, NULL, 'Tobias', NULL)<br />Department instantiated deptno=60<br />Department instantiatedDepartment instantiated[TopLink Fine]: 2006.01.01 07:28:07.553--UnitOfWork(4558657)--Connection(10769718)--Thread(Thread[main,5,main])--INSERT INTO DEPT (DEPTNO, DNAME, LOC) VALUES (60, 'Toys', 'Zoetermeer')<br /> deptno=60<br />[TopLink Fine]: 2006.01.01 07:28:07.573--UnitOfWork(4558657)--Connection(10769718)--Thread(Thread[main,5,main])--UPDATE EMP SET deptno = 60 WHERE (EMPNO = 2000)

Now what would happen if we were to persist the new Employee instead of the Department:

 

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EntityTransaction tx = hrmService.getEntityManager().getTransaction();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.begin();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Department toys = new Department();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDeptno(new Long(60));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setDname(&quot;Toys&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.setLocation(&quot;Zoetermeer&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Employee tobias = new Employee();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEname(&quot;Tobias&quot;);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setEmpno(new Long(2000));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setDepartment(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().persist(tobias);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Commit the transaction<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();

This ends with an exception: Exception
in thread "main" java.lang.IllegalStateException: During
synchronization a new object was found through a relationship that was
not marked cascade PERSIST
. This is the result of not having
specified the proper Cascade type for the ManyToOne end of the
relation, in the Employee Entity. If we change the relation
specification from

 

&nbsp;&nbsp;&nbsp; @ManyToOne(optional=false, fetch=FetchType.LAZY)<br />&nbsp;&nbsp;&nbsp; @JoinColumn(name=&quot;deptno&quot;)<br />&nbsp;&nbsp;&nbsp; public Department getDepartment() {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return department; <br />&nbsp;&nbsp;&nbsp; }

to
 

&nbsp;&nbsp;&nbsp; @ManyToOne(optional=false, cascade={CascadeType.PERSIST} ,fetch=FetchType.LAZY)<br />&nbsp;&nbsp;&nbsp; @JoinColumn(name=&quot;deptno&quot;)<br />&nbsp;&nbsp;&nbsp; public Department getDepartment() {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return department; <br />&nbsp;&nbsp;&nbsp; }

we should be back in business. The logging now is:

Department instantiatedDepartment instantiated[TopLink Fine]: 2006.01.01 07:34:42.511--UnitOfWork(18414151)--Connection(10769718)--Thread(Thread[main,5,main])--INSERT INTO DEPT (DEPTNO, DNAME, LOC) VALUES (60, 'Toys', 'Zoetermeer')<br />[TopLink Fine]: 2006.01.01 07:34:42.591--UnitOfWork(18414151)--Connection(10769718)--Thread(Thread[main,5,main])--INSERT INTO EMP (EMPNO, HIREDATE, JOB, MGR, COMM, SAL, ENAME, deptno) VALUES (2000, NULL, NULL, NULL, NULL, NULL, 'Tobias', 60)<br />

So
this time we have an implicit insert for the new Department. The
EntityManager knows that the Employee can only be successfully inserted
if the referenced Department is already created in the database, hence
this sequence.

So what now if we remove an Employee from the
staff of the Department and merge the Department? I add the following
lines to the above code:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.begin();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.getStaff().remove(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().merge(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Commit the transaction<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();<br />

When
I run the changed code, I see no effect of this operation whatsoever.
Only when I add the explicit setDepartment(null) for tobias do I see an
effect:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.begin();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; toys.getStaff().remove(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tobias.setDepartment(null);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().merge(toys);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrmService.getEntityManager().merge(tobias);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Commit the transaction<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();<br /><br />The Logging:
Department instantiatedDepartment instantiated[TopLink Fine]: 2006.01.01 07:44:48.202--UnitOfWork(18414151)--Connection(10769718)--Thread(Thread[main,5,main])--INSERT INTO DEPT (DEPTNO, DNAME, LOC) VALUES (60, 'Toys', 'Zoetermeer') <br />[TopLink Fine]: 2006.01.01 07:44:48.272--UnitOfWork(18414151)--Connection(10769718)--Thread(Thread[main,5,main])--INSERT INTO EMP (EMPNO, HIREDATE, JOB, MGR, COMM, SAL, ENAME, deptno) VALUES (2000, NULL, NULL, NULL, NULL, NULL, 'Tobias', 60) <br />[TopLink Fine]: 2006.01.01 07:44:48.292--UnitOfWork(13640204)--Connection(23376028)--Thread(Thread[main,5,main])--UPDATE EMP SET deptno = NULL WHERE (EMPNO = 2000)

So
clearly the OneToMany relation is used by the EntityManager to a
certain extent, but that does not include updating or deleting the
entities at the many end. We have seen that it may lead to insert of new
many-entities, but without setting the referencing column. This also
means in this case that we – our application – is responsible itself
for maintaining both relations: the staff collection for the OneToMany
relation as well as the department reference for the ManyToOne
relation. And we also see that it seems that a OneToMany by itself can
be used for retrieval, read-only purposes, but not for full data
manipulation. From this example, it would seem that the ManyToOne is
required. Mmmmm, let’s see whether that is actually true. From Banish Your Resistance to Persistence with the EJB 3.0 Persistence API by Rod Coffin

"All
relationships have an owning side. The owning side is used to
determine what updates need to be applied to the database.
Additionally, bidirectional relationships also have an inverse side. In
a one-to-one relationship the owning side is the side with the
reference. In a one-to-many relationship the many side must be the
owning side. And in a many-to-many relationship either side can be the
owning side. Regardless, it is the developer’s responsibility to keep
both sides of the relationship consistent with one another. In a
unidirectional relationship one entity has a reference to another,
but the second entity does not have a corresponding reference back to
the first. In a bidirectional relationship both entities have
references to each other. In a bidirectional relationship the inverse
side must refer back to the
owning side through the mappedBy attribute of the annotation used to
define the relationship.
"

It seems that we need a ManyToOne when we have a OneToMany.

Resources

Download the Sources for this article: ejb30_hrm_relations_src.zip 

How-To Configure the TopLink EJB 3.0 Mapping Annotations Date: 2/28/05 Author: Guy Pelletier, Oracle Technology Network

Kodo EJB – SolarMetric EJB 3.0 Persistence API Quick Reference Guide

Java Persistence and Swing, part 2 – Relationships by Martin Adamek (15 november 2005)

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

Comments are closed.