Actually I was simplifying the context from 1-M to 1-1 for discussion so
left groupBy attribute mistakenly.
The problem at hand is that Teacher has students (1:M). Student must
have some teacher assigned to him but teacher does not necessarily need
to have some student.
So I go ahead and load some teacher and it will load all students
associated with it as Teacher would have List<Student>. But what I want
is that student also have a reference to Teacher. Following code would
explain what I want:
class Teacher {
List<Student> students;
....
}
class Student {
Teacher teacher;
....
}
SQL Map config file
<resultMap id="TeacherResultMap" class="Teacher" groupBy="id" >
<result property="id" />
<result property="name" />
<result property="students" resultMap="JOB.StudentResultMap"/>
</resultMap>
<resultMap id="StudentResultMap" class="Student" >
<result property="id" column="stu_id"/>
<result property="name" column="stu_Name"/>
<result property="teacher" resultMap="JOB.TeacherResultMap"/>
</resultMap>
<select id="findTeacher" parameterClass="int"
resultMap="TeacherResultMap11">
select t.id , t.name , s.id as stu_id, s.name as stu_Name , s.tid
as stu_tid from Teacher t left join student s on t.id = s.tid where t.id
= #value#
</select>
The line in red would make cyclic reference to associate teacher with
student and stack overflow so I can't do that but effectively I want to
do that.
I understand your point where you tell me I could associate teacher for
each student in business logic but I am wondering if there is any way
that both sides of relationship could hold a reference of other side of
relationship implicitly.
OK. As far as I know iBATIS cannot be used to make the association
navigable in both directions without additional own code.
You should remove the "teacher" property from the StudenResultMap and
after getting the Teacher instance iterate over the list of Student
instances to invoke the setTeacher() method. The use of a RowHandler is
an alternative.
For further discussion I share my experience. Our team prefere use
LinkedHashMap<Integer, Student> instead of List<Student>. The advantage
of the map is that you can get the student by ID without iterating. The
ID is very often returned from some GUI component. We use LinkedHashMap
instead of HashMap to keep the order and to sort the elements of the map
when indicated. In some cases we use TreeMap, TreeMap<Date, ...> for
example. To avoid LEFT JOINs and reduce the amount of statements needed
to load complex objects in several variants we are loading objects from
each table in a separate step. In your case this would look like.
<sqlMap namespace="teacher">
<resultMap id="result" class="Teacher">
<result property="id" />
<result property="name" />
</resultMap>
<select id="select" parameterClass="int" resultMap="result">
SELECT
id,
name
FROM teacher
WHERE id = #value#
</select>
</sqlMap>
<sqlMap namespace="student">
<resultMap id="result" class="Student" >
<result property="id" />
<result property="name" />
</resultMap>
<select id="selectByTeacher" parameterClass="int" resultMap="result">
SELECT
id,
name
FROM student
WHERE tid = #value#
</select>
</sqlMap>
StudentDAOImpl
{
public List<Student> selectByTeacher(Integer teacherId)
{
return List<Student>
getSqlMapClientTemplate().queryForList("student.selectByTeacher",
teacherId)
}
}
TeacherDAOImpl
{
public Teacher select(Integer id)
{
return (Teacher)
getSqlMapClientTemplate().queryForList("teacher.select", id)
}
}
Integer teacherId = ...
Teacher teacher = this.teacherDAO.select(teacherId);
List<Student> studentList = this.studentDAO.selectByTeacher(teacherId);
Map<Integer, Student> studentMap = teacher.getStudentMap();
for (Student student : studentList)
{
student.setTeacher(teacher);
studentMap.put(student.getId(), student);
}
This is simplified because allows to query for 1 teacher only.