資料庫中實體之間通常有一對一、一對多、多對多等關係,最近搗鼓Mybatis完成一對多映射時就遇到了一些坑,花費了大量時間才從坑裡爬了出來,現在將其總結如下。

首先,假設我們要處理兩個實體類:TeacherStudent,一個老師可以教多個學生,但一個學生只能有一個老師。很明顯可以看出,TeacherStudent之間是一對多的關係。

1.創建這兩個實體類(Java),為了簡單起見,兩個類中均只有idname兩個主要欄位。

//Student.java
public class Student {
private Integer id;
private String name;
private Teacher teacher;//學生對應的老師
//getter and setter...
}

//Teacher.java
public class Teacher {
private Integer id;
private String name;
private List<Student> studentList;//老師所教的學生
//getter and setter...
}

2.創建兩張表並插入一些數據:

create table tb_student(
id int auto_increment,
name varchar(40) not null,
teacher_id int not null,
primary key (id),
constraint foreign key (teacher_id) references tb_teacher(id)
)default charset=utf8;
create table tb_teacher(
id int auto_increment,
name varchar(40) not null,
primary key (id),
constraint foreign key (teacher_id) references tb_teacher(id)
)default charset=utf8;
insert into tb_teacher(name) values (tom),(jack),(jhon);
insert into tb_student(name,teacher_id)
values (Li,1),(Zh,1),(Lei,1),(Liu,2),(Wang,2),(Song,3);

現在兩張表的內容分別為:

tb_teacher:

tb_student:

3.創建介面類和mapper文件

//StudentDao.java
//import ...
public interface StudentDao {
Student getStudentById(Integer id);
}

//TeacherDao.java
//import ...
public interface TeacherDao {
Teacher getTeacherById(Integer id);
}

mapper文件:

student-mapper.xml:

<mapper namespace="StudentDao">
<resultMap id="studentResultMap" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="teacher_id"
javaType="Teacher" resultMap="teacherResultMap" fetchType="lazy">
</association>
</resultMap>
<select id="getStudentById" resultMap="studentResultMap">
SELECT * FROM tb_student
WHERE id = #{id}
</select>
</mapper>

teacher-mapper.xml:

<mapper namespace="TeacherDao">
<resultMap id="teacherResultMap" type="Teacher">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="studentList" javaType="ArrayList"
column="id" ofType="Student"
resultMap="studentResultMap" fetchType="lazy">
</collection>
</resultMap>
<select id="getTeacherById" resultMap="teacherResultMap">
SELECT * FROM tb_teacher t, tb_student s
WHERE t.id = #{id} AND s.teacher_id = t.id
</select>
</mapper>

現在,所有的配置已經初步完成了,定義一個測試類,由於使用了Spring framework,這個測試可以非常方便的完成。

//TestTeacherDao.java
//import ...
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestTeacherDao {
@Autowired
private TeacherDao teacherDao;
@Test
public void testGetTeacherById() {
Teacher teacher = teacherDao.getTeacherById(1);
System.out.println(teacher.getName());
//獲取id為1的老師對應的學生列表
for (Student student : teacher.getStudentList()) {
System.out.println(" " + student.getName());
}
}
}

我們首先在Mysql控制台上輸入查詢指令查詢id為1的老師對應的學生,得到的結果是這樣的:

現在運行上面的測試代碼,卻得到了這樣的結果:

tom
tom

結果非常離譜!這個結果有兩個問題:

  1. 測試代碼中我們想要獲取的是學生的名字,但結果卻是老師的。
  2. 數量不對。我們的結果應該有3個對應的學生,但這裡只有1個。

那麼問題出在哪裡呢?

問題就在於名字上。說了這麼多,終於扯到了今天這篇文章的主題了:名字很重要

因為TeacherStudent實體類中的欄位有著相同的名字idname,所以就導致Mybatis在進行結果映射的時候不知道該怎麼將表中的column和類中的property對應起來,因此上面第一個問題的原因就是表欄位的名字一樣,導致了最終映射的錯誤,將屬於老師的名字tom映射到了學生的name屬性上。

那麼第二個問題又是怎麼導致的呢?這就不得不說Mybatis mapper中的resultMap了。在resultMap中,有兩個在數據表和類屬性之間進行映射的關鍵字,分別是idresult,從上面的mapper文件中我們可以看到這兩者的身影,那麼這兩者有什麼區別呢?官方文檔是這樣說的:

The only difference between the two is that id will flag the result as an identifier property to be used when comparing object instances. This helps to improve general performance, but especially performance of caching and nested result mapping (i.e. join mapping).

看完不明所以,實際上這段話的意思很簡單,idresult之間唯一的區別就是id可以用來對結果進行比較。拿我們上面的例子來說,student-mapper.xml文件中,我們在sturentResultMap這個resultMap中使用了id在欄位id和屬性id進行映射:

<resultMap id="studentResultMap" type="Student">
<id property="id" column="id"/>
...
</resultMap>

但實際上最後的這個id對應的是我們查表所得結果的第一列的id,可以看到三條結果的第一列id值都是1,這時候Mybatis根據這個id值進行比較發現這三條結果相等!然後當然就只會保留一個結果了!

至此,兩個問題的原因我們都找到了。有了原因問題就能迎刃而解,方法很簡單,只需要能夠區分相同的欄位就行了。比如我們將表tb_teacher中的欄位名稱起一個別名,如t_id,t_name

<mapper namespace="TeacherDao">
<resultMap id="teacherResultMap" type="Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
<collection property="studentList" javaType="ArrayList"
column="id" ofType="Student"
resultMap="studentResultMap" fetchType="lazy">
</collection>
</resultMap>
<select id="getTeacherById" resultMap="teacherResultMap">
SELECT t.id t_id,t.name t_name,s.* FROM tb_teacher t, tb_student s
WHERE t_id = #{id} AND s.teacher_id = t_id
</select>
</mapper>

重新運行測試程序,就能得到正確地結果了:

tom
Li
Zh
Lei

所以以後使用Mybatis進行資料庫操作時一定要注意名字啊!


推薦閱讀:
相关文章