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 @Table(name="EMP")
and for Property to Column mapping and for Primary Key indicator:
@Id @Column(name="EMPNO", nullable=false)
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: …
@ManyToOne(optional=false) @JoinColumn(name="deptno") public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; }
… in our HrmClient code:
HrmService hrmService = new HrmService(); // use the predefined service method for finding all employees // and printing all their names List<Employee> emps = hrmService.findAllEmp(); for (Employee e : emps) { System.out.println(e.getEname()+" in Department "+e.getDepartment().getDname()); }
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 [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 [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) Department instantiated deptno=20 Department instantiated deptno=20 Department instantiated deptno=20 SMITH(7369) in Department RESEARCH [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) Department instantiated deptno=30 Department instantiated deptno=30 Department instantiated deptno=30 ALLEN(7499) in Department SALES WARD(7521) in Department SALES JONES(7566) in Department RESEARCH MARTIN(7654) in Department SALES BLAKE(7698) in Department SALES [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) Department instantiated deptno=10 Department instantiated deptno=10 Department instantiated deptno=10 CLARK(7782) in Department ACCOUNTING SCOTT(7788) in Department RESEARCH KING(7839) in Department ACCOUNTING TURNER(7844) in Department SALES ADAMS(7876) in Department RESEARCH JAMES(7900) in Department SALES FORD(7902) in Department RESEARCH MILLER(7934) in Department ACCOUNTING
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.
public class HrmClient {
public static void main(String[] args) {
HrmService hrmService = new HrmService();
Employee allen = hrmService.findEmployee(new Long(7499));
Employee blake = hrmService.findEmployee(new Long(7698));
System.out.println("Departments for Allen:"+allen.getDepartment().getDname()
+" and Blake"+blake.getDepartment().getDname());
System.out.println("Are the Departments equal: "+(allen.getDepartment()==blake.getDepartment()?"Equal":"Not equal"));
...
Note: this code relies on the method findEmployee in HrmService:
public Employee findEmployee(Long empno) { return getEntityManager().find(Employee.class, empno); }
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:
import javax.persistence.OneToMany;
import javax.persistence.CascadeType;
...
private Collection<Employee> staff;
@OneToMany(mappedBy="department", cascade={CascadeType.ALL})
public Collection<Employee> getStaff() {
return staff;
}
public void setStaff(Collection staff) {
this.staff = staff;
}
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 { public static void main(String[] args) { HrmService hrmService = new HrmService(); Department sales = hrmService.findDepartment(new Long(30)); System.out.println("Sales "+sales.getDname()+" staff:"+sales.getStaff()); System.out.println("Sales "+sales.getDname()+" staffsize:"+sales.getStaff().size()); Employee allen = hrmService.findEmployee(new Long(7499)); Employee blake = hrmService.findEmployee( new Long(7698)); System.out.println("Departments for Allen: "+allen.getDepartment().getDname()+" and Blake "+blake.getDepartment().getDname()); System.out.println("Are the Departments equal: "+(allen.getDepartment()==blake.getDepartment()?"Equal":"Not equal")); System.out.println("Is the Allen object in the Sales Staff collection? "+(sales.getStaff().contains(allen)?"Yes":"No")); } }
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:
EntityTransaction tx = hrmService.getEntityManager().getTransaction(); tx.begin(); Employee tobias = new Employee(); tobias.setEname("Tobias"); tobias.setEmpno(new Long(2000)); Department toys = new Department(); toys.setDeptno(new Long(60)); toys.setDname("Toys"); toys.setLocation("Zoetermeer"); Collection<Employee> staff = new ArrayList<Employee>(); staff.add(tobias); toys.setStaff(staff); hrmService.getEntityManager().persist(toys); // Commit the transaction tx.commit();
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') [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)
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?:
// inside a transaction, create a new employee - persist it - and a new department - and persist it! EntityTransaction tx = hrmService.getEntityManager().getTransaction(); tx.begin(); Employee tobias = new Employee(); tobias.setEname("Tobias"); tobias.setEmpno(new Long(2000)); hrmService.getEntityManager().persist(tobias); Department toys = new Department(); toys.setDeptno(new Long(60)); toys.setDname("Toys"); toys.setLocation("Zoetermeer"); Collection<Employee> staff = new ArrayList<Employee>(); staff.add(tobias); toys.setStaff(staff); hrmService.getEntityManager().persist(toys); // Commit the transaction tx.commit();
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:
EntityTransaction tx = hrmService.getEntityManager().getTransaction(); tx.begin(); Employee tobias = new Employee(); tobias.setEname("Tobias"); tobias.setEmpno(new Long(2000)); hrmService.getEntityManager().persist(tobias); hrmService.getEntityManager().flush(); Department toys = new Department(); toys.setDeptno(new Long(60)); toys.setDname("Toys"); toys.setLocation("Zoetermeer"); Collection<Employee> staff = new ArrayList<Employee>(); staff.add(tobias); toys.setStaff(staff); tobias.setDepartment(toys); hrmService.getEntityManager().persist(toys); // Commit the transaction 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 [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) Department instantiated deptno=60 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') deptno=60 [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:
EntityTransaction tx = hrmService.getEntityManager().getTransaction(); tx.begin(); Department toys = new Department(); toys.setDeptno(new Long(60)); toys.setDname("Toys"); toys.setLocation("Zoetermeer"); Employee tobias = new Employee(); tobias.setEname("Tobias"); tobias.setEmpno(new Long(2000)); tobias.setDepartment(toys); hrmService.getEntityManager().persist(tobias); // Commit the transaction 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
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="deptno") public Department getDepartment() { return department; }
to
@ManyToOne(optional=false, cascade={CascadeType.PERSIST} ,fetch=FetchType.LAZY) @JoinColumn(name="deptno") public Department getDepartment() { return department; }
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') [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)
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:
tx.begin(); toys.getStaff().remove(tobias); hrmService.getEntityManager().merge(toys); // Commit the transaction tx.commit();
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:
tx.begin(); toys.getStaff().remove(tobias); tobias.setDepartment(null); hrmService.getEntityManager().merge(toys); hrmService.getEntityManager().merge(tobias); // Commit the transaction tx.commit(); 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') [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) [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)