前言
在上篇MyBatis基礎篇中我們獨立使用MyBatis構建了一個簡單的數據庫訪問程序,可以實現單表的基本增刪改查等操作,通過該實例我們可以初步了解MyBatis操作數據庫需要的一些組成部分(配置文件、實體類、SQL映射文件、Mapper接口等等)和重要對象(SqlSession、Mapper實例等等)。有了整體認知后,我們就可以進一步深入學習MyBatis的使用,resultMap本文主要圍繞resultMap展開。
resultMap作為MyBatis的Sql映射文件中重要的元素之一,主要用來實現復雜的結果映射,其子元素結構如下:
constructor
constructor是構造器的意思,對反射有基本了解的都應該不會陌生。借此我們先回顧一下前面定義的一個簡單resulMap映射內容
<resultMap type="person" id="personResultMap" > <id column="id" property="id" /> <result column="name" property="name" /> <result column="gender" property="gender" /> </resultMap>
上面是一個典型的在resultMap中定義數據表與實體類的映射關系,type這里用的別名指向Person類,id為該映射的唯一標識,用於在后面我們的定義語句中引用,內部的id和result分別對應主鍵和普通字段定義,column指數據表中字段名,property指實體類中屬性名。在之前的示例中,通過這個一個映射關系,我們查詢出來的結果就轉化為了一個Person類對象。
這個Person類對象並非由我們創建出來,而是由mybatis調用了Person類的默認無參構造函數創建對象,再調用set方法為對象賦值,這樣才返回了我們想要的結果。下面結合之前的示例,做一些改動以便觀察。
package com.mmm.pojo; /** * Person實體類 * */ public class Person { private String id; private String name; private String gender; public String getId() { return id; } public void setId(String id) { this.id = id; System.out.println("為主鍵屬性id賦值"); } public String getName() { return name; } public void setName(String name) { this.name = name; System.out.println("為屬性name賦值"); } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; System.out.println("為屬性gender賦值"); } public Person() { System.out.println("調用無參構造創建對象"); } }
<select id="selectById"resultMap="personResultMap"> select * from `person` where id = #{id} </select>
調用該查找方法后會看到控制台輸出如下信息:
說明確實是mybatis調用了這些方法幫我們創建了對象,基於構造器。說起構造器,在我們定義的pojo實體類中,簡單的私有屬性加上set/get方法聲明即完成,在java類中,會默認提供一個無參的構造方法,例如這里的Person類
public Person(){}
我們使用語句 Person p = new Person();這里即調用的該無參構造。方法都可以重載,構造方法也不例外。但是很重要的一點,我們在重載構造方法后,原本默認提供的無參構造就無法使用了,需要顯示聲明,這點下面會給出示例。resultMap中constructor就是基於重載的帶參構造方法創建對象,如下:
<resultMap type="person" id="personResultMap" > <constructor> <idArg column="id" javaType="string" /> <arg column="name" javaType="string" /> <arg column="gender" javaType="string" /> </constructor> </resultMap>
在實體類中添加重載的帶參構造方法
public Person(String id, String name, String gender) { this.id = id; this.name = name; this.gender = gender; System.out.println("直接調用帶參構造函數創建對象"); }
再次調用查找方法后會看到控制台輸出如下信息:
這樣一來,使用constructor同樣拿到了數據。
這個時候如果我們把實體類中的默認無參構造方法的顯示聲明刪除,即只有一個帶參的構造方法,這樣我們再把Sql映射文件中<resultMap>內容還原成文中一開始定義的典型<id><result>等,再調用方法,為了更明顯,在地址欄中通過controller層層調用到該方法,報錯乍一看,一大串異常,其實標題末尾一句話就出來了
不存在該方法,該方法指的就是Person實體類中的無參構造方法,在之前我們沒有顯示聲明沒有問題,那是因為在類中如果沒有任何的構造方法聲明,會默認提供一個無參構造;但是我們使用construtor的時候在實體類中重載了一個帶參的構造方法,使得原本默認的無參構造無法使用了,這里如果還要需要使用,可以在實體類中顯示聲明。
id、result
前面的單表典型基本映射就是由這兩個元素組成,所以應該比較好理解。id和result都用於將表中字段和實體類中屬性建立起映射關系,區別是id是用於主鍵字段的映射,result用於普通字段映射。兩個元素中都有5個屬性供定義,分別是column、property、javaType、jdbcType、typeHandler。
column:這里用於單表查詢的話可以簡單的理解為數據表中字段,前面也是類似處理,但是實際上並不是這么簡單,例如我們在寫SQL語句時經常會使用到別名,別名的好處就不復述了,特別是進行多表查詢時,這里我們在SQL映射文件的<select>語句中也可以使用別名,這個別名與這里的column就對應上了,不注意這點的話可能出問題,后面介紹關聯查詢會有示例。
property:實體類中定義的與表字段對應的屬性
javaType:類型這里可以使用全名,也可以使用別名,例如上面的string就是一個別名。我們在定義基本的實體類映射時,mybatis一般可以自動確定類型。
jdbcType:這里是指針對數據表字段方的jdbc類型,mybatis中支持下面的 JDBC 類型。
typeHandler:在使用數據庫創建表,定義字段名,選擇字段類型時,例如MySql中字符串類型varchar,然后我們使用mybatis映射到實體類中變成Java字符串類型String,這個轉換過程是怎么實現的?其中mybatis就已經預定義了一些類型處理器,也就是這個typeHandler,我們也可以根據需要自定義一些類型處理器。
association(對一)
association意思為聯系、關聯,針對單個(一個)對象,典型的一對一、多對一關聯映射關系都可以通過該元素實現,下面是基本定義內容。
<association property="關聯的對象在本對象實體類中的屬性名" column="關聯表的主鍵在本表中的字段名" javaType="關聯對象的實體類類型(可用別名)"> <id column="ID" property="id" /> <result column="數據表字段名" property="實體類屬性名" />
<result column="數據表字段名" property="實體類屬性名" />
... </association>
通過上面的關聯定義,然后在<select>元素中定義多表關聯查詢語句即可實現關聯查詢效果。這個<association>內部的<id><result>等元素,我們不一定要這樣定義,也可以通過額外定義一個resultMap填入這些元素,然后<association>有一個resultMap屬性,值就填額外的reslutMap的id,這樣也可以實現關聯映射。除此之外,也可以通過一個關聯的嵌套查詢語句實現,即額外定義一個針對關聯表的<select>元素,然后<association>有一個select屬性,值就填<select>的id,這樣同樣能實現。
collection(對多)
collection意為集合,這里主要用於關聯多個對象的映射關系定義,例如典型的一對多,下面是基本定義內容。
<collection property="關聯的對象在本對象實體類中的屬性名" column="本表的主鍵在關聯表中的字段名" ofType="關聯集合中對象的實體類類型(可用別名)"> <id column="ID" property="id" /> <result column="數據表字段名" property="實體類屬性名" /> <result column="數據表字段名" property="實體類屬性名" />
... </collection>
類似上面的association,也可以額外定義<select>元素,然后引入,從而實現關聯查詢。
關聯映射應用得較多,關聯屬性也相對復雜一些,光憑文字和片段描述很難快速理解,下面以一個一對多和多對一結合的示例來整體應用一次。
關聯映射實例(一對多/多對一)
背景關系
這里以部門作為查詢的實體對象,從圖中不難理解,部門與公司是多對一關系,部門與員工是一對多關系,我們查詢一個部門對象時,要把這個部門所屬的公司信息查出來,還要把部門下面的所有員工信息查出來。
准備工作
這里對象分公司、部門、員工一共三類,所以首先准備三張數據表,結構如下
這里要理解一點,基於主鍵關聯,不論是一對多,還是多對一,在“多“”的一方表中,都有一個與“一”的一方表主鍵對應的字段,例如這里的部門表中COMPANY_ID和員工表中的DEPT_ID,用來關聯對應表。
新建maven工程,這里不需要建web項目,普通的java項目就行。pom依賴可以參照SSM框架開發web項目系列(一) 環境搭建篇。整體結構如下所示
然后新建三個對象實體類,如下所示
package com.mmm.pojo; //公司 public class Company { private Integer id; //主鍵 private String companyName; //公司名稱 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } @Override public String toString() { return "Company [id=" + id + ", companyName=" + companyName + "]"; } }
package com.mmm.pojo; import java.util.List; //部門 public class Dept { private Integer id; //主鍵 private String deptName; //部門名稱 private Company company; //部門所屬公司---------多對一 private List<Emp> empList; //部門下面員工---------一對多 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public Company getCompany() { return company; } public void setCompany(Company company) { this.company = company; } public List<Emp> getEmpList() { return empList; } public void setEmpList(List<Emp> empList) { this.empList = empList; } @Override public String toString() { return "Dept [id=" + id + ", deptName=" + deptName + ", company=" + company + ", empList=" + empList + "]"; } }
package com.mmm.pojo; //員工實體類 public class Emp { private Integer id; //主鍵 private String name; //員工姓名 private String gender; //員工性別 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Emp [id=" + id + ", name=" + name + ", gender=" + gender + "]"; } }
配置文件(mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 這里可以定義類的別名,在mapper.xml文件中應用會方便很多 --> <typeAliases> <typeAlias alias="emp" type="com.mmm.pojo.Emp" /> <typeAlias alias="dept" type="com.mmm.pojo.Dept" /> <typeAlias alias="company" type="com.mmm.pojo.Company" /> </typeAliases> <!-- 環境配置 --> <environments default="envir"> <environment id="envir"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/ssm?characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/mmm/mapper/deptMapper.xml"/> </mappers> </configuration>
mapper接口及對應sql映射文件
package com.mmm.mapper; import com.mmm.pojo.Dept; public interface DeptMapper { public Dept selectById(Integer id); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mmm.mapper.DeptMapper"> <resultMap type="dept" id="deptResultMap"> <!-- 下面的column就是前面說的要注意的,如果簡單的認為是字段名會出問題 --> <id column="d_id" property="id" /> <result column="d_name" property="deptName" /> <!-- 一個公司下面多個部門,部門與公司,多對一關系 --> <association property="company" column="COMPANY_ID" javaType="company" resultMap="companyResultMap"> <!-- <id column="ID" property="id" /> <result column="COMPANY_NAME" property="companyName" /> --> </association> <!-- 一個部門下有多個員工,部門與員工,一對多關系 --> <collection property="empList" column="DEPT_ID" ofType="emp"> <id column="ID" property="id" /> <result column="EMP_NAME" property="name" /> <result column="EMP_GENDER" property="gender" /> </collection> </resultMap> <!-- 也可以通過類似下面的定義然后引入到association中,association的resultMap屬性值對應下面resultMap的id --> <!-- <resultMap type="company" id="companyResultMap"> <id column="ID" property="id" /> <result column="COMPANY_NAME" property="companyName" /> </resultMap> --> <!-- 根據主鍵id查找記錄 --> <select id="selectById" resultMap="deptResultMap"> select e.*,d.ID AS d_id,d.DEPT_NAME AS d_name,c.* from `TBL_EMP` e, `TBL_DEPT` d, `TBL_COMPANY` c where d.ID = #{id} and d.COMPANY_ID = c.id and d.ID = e.DEPT_ID; </select> </mapper>
最后,測試一下是否能實現關聯查詢
package com.mmm.test; import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import com.mmm.mapper.DeptMapper; import com.mmm.pojo.Dept; public class TestMyBatis { @Test public void testCore() throws IOException { //直接實例SqlSessionFactoryBuilder對象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //MyBatis配置文件路徑 String path = "mybatis-config.xml"; //通過路徑獲取輸入流 Reader reader = Resources.getResourceAsReader(path); //通過reader構建sessionFactory SqlSessionFactory sessionFactory = builder.build(reader); //獲取SqlSession對象 SqlSession sqlSession = sessionFactory.openSession(); //獲取Mapper實例 DeptMapper mapper = sqlSession.getMapper(DeptMapper.class); //根據id查找制定的部門,並關聯查詢出其公司和員工信息 Dept dept = mapper.selectById(10001); System.out.println(dept); } }
控制台輸出信息過長,復制下來粘貼如下內容:
Dept [id=10001, deptName=資產部, company=Company [id=10003, companyName=公司一], empList=[Emp [id=10001, name=員工一, gender=男], Emp [id=10002, name=員工二, gender=男], Emp [id=10003, name=員工三, gender=男]]]
可以看到,至此基本實現了,查詢部門時,也關聯查詢出了所屬公司信息、下面所有部門員工信息。
我們在瀏覽網站,點擊鏈接跳轉,每個頁面的刷新,大多是不斷在請求查詢數據庫中的數據,當然也不全是查詢,例如會有日志記錄,也是插入操作等等。查詢是一個比較重要的內容,怎樣關聯查詢,怎么優化查詢語句,減少查詢次數,提高查詢速度等等都是需要去思考和學習的地方。