Mybatis 中對於多表查詢提供了非常強大的實現方式,主要是通過resultMap的結果映射對於多表查詢后的返回值進行封裝,讓我們來看一下官網上對於resultMap的解釋:resultMap 元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 數據提取代碼中解放出來,並在一些情形下允許你進行一些 JDBC 不支持的操作。實際上,在為一些比如連接的復雜語句編寫映射代碼的時候,一份 resultMap 能夠代替實現同等功能的長達數千行的代碼。ResultMap 的設計思想是,對於簡單的語句根本不需要配置顯式的結果映射,而對於復雜一點的語句只需要描述它們的關系就行了。通過描述對象之間的關系將查詢后的結果映射到我們定義的實體類中。
首先介紹一下本例中的實體類以及其映射關系,Demo中存在User類以及Account類,其關系為一個用戶對應零個、一個或者多個賬戶,賬戶中為了簡單單單保存用戶的賬戶余額以及所屬用戶的ID。我們實現的查詢的目標為:每次查詢一個賬戶的時候同時將其所屬的用戶信息也展示出來。為了更好的幫助理解,我們將展示一種非mybatis方式以及兩種mybatis方式的實現來實現。User類以及Accoun類t的內容如下:

1 import java.io.Serializable; 2 3 public class Account implements Serializable{ 4 private Integer id; 5 private Integer uid; 6 private Double money; 7 private User user; 8 9 public User getUser() { 10 return user; 11 } 12 13 public void setUser(User user) { 14 this.user = user; 15 } 16 17 public Integer getId() { 18 return id; 19 } 20 21 public void setId(Integer id) { 22 this.id = id; 23 } 24 25 public Integer getUid() { 26 return uid; 27 } 28 29 public void setUid(Integer uid) { 30 this.uid = uid; 31 } 32 33 public Double getMoney() { 34 return money; 35 } 36 37 public void setMoney(Double money) { 38 this.money = money; 39 } 40 41 @Override 42 public String toString() { 43 return "Account{" + 44 "id=" + id + 45 ", uid=" + uid + 46 ", money=" + money + 47 '}'; 48 } 49 }

1 import java.io.Serializable; 2 import java.util.Date; 3 4 public class User implements Serializable{ 5 private Integer id; 6 private String username; 7 private Date birthday; 8 private String sex; 9 private String address; 10 11 public Integer getId() { 12 return id; 13 } 14 15 public void setId(Integer id) { 16 this.id = id; 17 } 18 19 public String getUsername() { 20 return username; 21 } 22 23 public void setUsername(String username) { 24 this.username = username; 25 } 26 27 public Date getBirthday() { 28 return birthday; 29 } 30 31 public void setBirthday(Date birthday) { 32 this.birthday = birthday; 33 } 34 35 public String getSex() { 36 return sex; 37 } 38 39 public void setSex(String sex) { 40 this.sex = sex; 41 } 42 43 public String getAddress() { 44 return address; 45 } 46 47 public void setAddress(String address) { 48 this.address = address; 49 } 50 51 @Override 52 public String toString() { 53 return "User{" + 54 "id=" + id + 55 ", username='" + username + '\'' + 56 ", birthday=" + birthday + 57 ", sex='" + sex + '\'' + 58 ", address='" + address + '\'' + 59 '}'; 60 } 61 }
數據庫的建表語句如下:
1 DROP TABLE IF EXISTS user; 2 3 CREATE TABLE user ( 4 id INT(11) NOT NULL auto_increment, 5 username VARCHAR(32) NOT NULL COMMENT '用戶名稱', 6 birthday datetime default NULL COMMENT '生日', 7 sex char(1) default NULL COMMENT '性別', 8 address varchar(256) default NULL COMMENT '地址', 9 PRIMARY KEY (id) 10 )ENGINE=InnoDB default CHARSET=utf8 11 INSERT INTO `user` VALUES ('41', '老王', '2018-02-27 17:47:08', '男', '石家庄'); 12 INSERT INTO `user` VALUES ('45', '老李', '2018-02-27 17:47:08', '男', '石家庄'); 13 INSERT INTO `user` VALUES ('46', '老郭', '2018-02-27 17:47:08', '男', '石家庄'); 14 15 DROP TABLE IF EXISTS account; 16 CREATE TABLE account( 17 ID int(11) NOT NULL COMMENT '編號', 18 UID INT(11) DEFAULT NULL COMMENT '用戶編號', 19 MONEY DOUBLE DEFAULT NULL COMMENT '金額', 20 PRIMARY KEY (ID), 21 KEY FK_Reference_8 (UID), 22 CONSTRAINT FK_Reference_8 FOREIGN KEY (UID) REFERENCES user (id) 23 )ENGINE=INNODB DEFAULT CHARSET=utf8 24 25 INSERT INTO accountc (ID,UID,MONEY) VALUES (1,46,1000),(2,45,1000),(3,46,2000);
搭建項目的過程就不展示了,主要的核心實體類和對應的數據庫表如上,接下來我們展示我們所要展示的三種方式實現一對一的聯表查詢。
1.非mybatis的高級結果映射方式實現聯表查詢。
這種方式的原理為通過創建一個新的類AccountUser類繼承Account類並在AccountUser類中添加我們想要查詢的User的信息,並且在賬戶查詢的Dao.xml文件中配置相應的sql語句即可實現。假如我們查詢Account的信息的時候同時想要查詢用戶的名稱以及地址,那就在AccountUser的類中聲明用戶的名稱以及地址。這種實現方式只是作為一種拓展的實現方式,在實際使用過程中並不推薦使用。
(1)聲明AccountUser類

1 public class AccountUser extends Account { 2 private String username; 3 private String address; 4 5 public String getUsername() { 6 return username; 7 } 8 9 public void setUsername(String username) { 10 this.username = username; 11 } 12 13 public String getAddress() { 14 return address; 15 } 16 17 public void setAddress(String address) { 18 this.address = address; 19 } 20 21 @Override 22 public String toString() { 23 return super.toString() + " "+"AccountUser{" + 24 "username='" + username + '\'' + 25 ", address='" + address + '\'' + 26 '}'; 27 } 28 }
需要注意的是該類繼承了Account類,聲明了我們需要的User類中的用戶名稱以及地址,對AccountUser類的toString()方法進行了改造,添加了super.toString(),方便我們打印的時候可以打印出從父類中繼承的屬性的屬性值。
(2)在AccountDao類中聲明查詢賬戶信息的方法
/** * 查找所有賬戶同時包含用戶的姓名和地址 * @return */ List<AccountUser> findAllAccountUser();
(3)在AccountDao.xml中配置findAllAccountUser方法的實現
<select id="findAllAccountUser" resultType="com.example.domain.AccountUser"> SELECT a.*,u.username,u.address FROM USER u,account a WHERE a.UID= u.id; </select>
(4)測試該方法
@Test public void findAllAccounUsertTest(){ List<AccountUser> accountList = accountDao.findAllAccountUser(); for (AccountUser account:accountList){ System.out.println(account); } }
測試結果:
2.通過Mybatis中的高級結果映射的resultMap的關聯屬性(association)來實現多表的一對一查詢。
關聯屬性主要用來處理“有一個”類型的關系,關聯的關鍵之處是我們需要告訴 MyBatis 如何加載關聯。在MyBatis 中有兩種不同的方式加載關聯:一是嵌套 Select 查詢:通過執行另外一個 SQL 映射語句來加載期望的復雜類型。二是嵌套結果映射:使用嵌套的結果映射來處理連接結果的重復子集。通過這兩種不同的方式衍生出兩種不同的方式去實現多表的一對一查詢。
1.關聯的嵌套SELECT查詢
(1)因為我們要實現的是在查詢賬戶的時候期望可以得到賬戶所屬用戶的某些信息,所以我們需要在Account類中聲明User對象,用來將查詢到的結果進行封裝。如下
private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; }
(2)AccountDao類中添加查詢的方法的聲明。
/** * 查找所有賬戶同時包含用戶的所有信息 * @return */ List<Account> findAll();
(3)在AccountDao.xml中配置findAll方法的的結果映射。首先聲明結果映射關系resultMap,resultMap的id為該結果映射的唯一標識,type為結果類的完全限定名,resultMap中的屬性說明:id 和 result 元素都將一個列的值映射到一個簡單數據類型(String, int, double, Date 等)的屬性或字段。這兩者之間的唯一不同是,id 元素表示的結果將是對象的標識屬性,這會在比較對象實例時用到。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候。id和result中的屬性說明:property
映射到列結果的字段或屬性,其實就是實體類中屬性的名稱。column是指數據庫中的列名,對應實體類的屬性。在下面的<id property="id" column="aid"/>中的column屬性的值aid沒有完全匹配上數據中的id,是因為在查詢語句中對account中的id字段設置了別名aid。association的屬性的 property為user對應實體類中聲明的user對象,其類型使用javaType屬性指定為User類,column為數據表的列名,並作為參數傳遞給此 select 語句。select屬性用於加載復雜類型屬性的映射語句的 ID,它會從 column 屬性中指定的列檢索數據。
<resultMap id="accountUserMap" type="com.example.domain.Account"> <id property="id" column="aid"/> <result property="uid" column="uid"/> <result property="money" column="money"/> <!--關聯的嵌套的select查詢--> <association property="user" javaType="com.example.domain.User" column="uid" select="selectUser"/> </resultMap>
(4)在AccountDao.xml中配置結果映射中的<association property="user" javaType="com.example.domain.User" column="uid" select="selectUser"/>的select="selectUser"的實現以及findAll方法的實現,
<select id="selectUser" resultType="user"> SELECT * FROM USER WHERE ID = #{id}; </select> <select id="findAll" resultMap="accountUserMap"> SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE a.UID= u.id; </select>
這里我們有兩個 select 查詢語句:一個用來加載賬戶信息(Account),另外一個用來加載用戶信息(User),而且accountUserMap的結果映射描述了應該使用 selectUser 語句加載它的 user屬性,其它的列名和屬性名相匹配的屬性將會被自動加載。
(5)查詢測試
@Test public void findAllTest(){ List<User> userList = userDao.findAll(); for (User user: userList){ System.out.println(user); } }
測試結果:
2.關聯的嵌套結果映射實現1。
(1)(2)步驟是上一方法是相同的。
(3)主要是修改了上一種方式中第三步中的resultMap中的association關聯屬性,將其替換為:<association property="user" javaType="com.example.domain.User" column="uid" resultMap="userMap"/>,在association 中添加了resultMap="userMap"屬性,userMap為結果映射的 ID,可以將此關聯的嵌套結果集映射到一個合適的對象中,也就是將關聯屬性user的結果映射到映射ID為userMap的resultMap中。userMap的聲明如下:
<resultMap id="userMap" type="com.example.domain.User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="birthday" column="birthday" jdbcType="DATE"/> <result property="sex" column="sex"/> <result property="address" column="address"/> </resultMap>
(4)AccountDao.xml的findAll方法的映射則只需要findAll方法,不再需要上一個方式中的selectUser映射的方法
<select id="findAll" resultMap="accountUserMap"> SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE a.UID= u.id; </select>
(5)(6)查詢代碼以及測試結果不再貼出
3.關聯的嵌套結果映射實現2。
第二種實現方式中使用了外部的結果映射元素來映射關聯。這使得 User的結果映射可以被重用。 然而,如果我們不需要重用它(在上個例子中他是userMap),或者你更喜歡將你所有的結果映射放在一個具有描述性的結果映射元素中。 你可以直接將結果映射作為子元素嵌套在內。
(1)(2)步驟是上一方法是相同的。
(3)仍然是修改了上一種方式中第三步中的resultMap結果映射中的association關聯屬性,將其替換如下:
<!--關聯的嵌套的結果映射2--> <association property="user" javaType="com.example.domain.User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="birthday" column="birthday" jdbcType="DATE"/> <result property="sex" column="sex"/> <result property="address" column="address"/> </association>
這樣實現與第二種實現大同小異,只是將關聯對象的屬性配置直接在association中進行了配置。
(4)AccountDao.xml的findAll方法的映射的findAll方法
<select id="findAll" resultMap="accountUserMap">
SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE a.UID= u.id;
</select>
(5)(6)查詢代碼以及測試結果不再貼出
總結:通過上述例子可以初步窺探了Mybatis中多表聯查(一對一)的使用方式,主要是通過resultMap的高級結果映射來實現的,在本例中最關鍵的屬性是resultMap的關聯屬性association,association也是我們告訴Mybatis對象之間的關系的橋梁,同時也介紹了resultMap的屬性的說明,通過解釋其屬性再加上Demo可以更好的理解結果映射的含義以及使用,這只是最簡單的一種使用方式,以后會詳細介紹一對多、多對多、多對一等復雜情況在Mybatis中的如何查詢映射。
參考網址:mybatis中文官網 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html