SSM框架開發web項目系列(三) MyBatis之resultMap及關聯映射


  前言

  在上篇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=男]]]

  可以看到,至此基本實現了,查詢部門時,也關聯查詢出了所屬公司信息、下面所有部門員工信息。

  我們在瀏覽網站,點擊鏈接跳轉,每個頁面的刷新,大多是不斷在請求查詢數據庫中的數據,當然也不全是查詢,例如會有日志記錄,也是插入操作等等。查詢是一個比較重要的內容,怎樣關聯查詢,怎么優化查詢語句,減少查詢次數,提高查詢速度等等都是需要去思考和學習的地方。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM