天天看點

MyBatis架構核心之(四)Mapper檔案使用resultMap及多表查詢四、resultMap與多表查詢(mapper.xml檔案)

四、resultMap與多表查詢(mapper.xml檔案)

一、resultMap簡介

MyBatis是基于“資料庫結構不可控”的思想建立的,也就是我們希望資料庫遵循第三範式或BCNF,但實際事與願違,那麼結果集映射就是MyBatis為我們提供這種理想與現實間轉換的手段了,而resultMap就是結果集映射的配置标簽了。

1.從SQL查詢結果到領域模型實體                  

  在深入ResultMap标簽前,我們需要了解從SQL查詢結果集到JavaBean或POJO實體的過程。

  1. 通過JDBC查詢得到ResultSet對象

  2. 周遊ResultSet對象并将每行資料暫存到HashMap執行個體中,以結果集的字段名或字段别名為鍵,以字段值為值

  3. 根據ResultMap标簽的type屬性通過反射執行個體化領域模型

  4. 根據ResultMap标簽的type屬性和id、result等标簽資訊将HashMap中的鍵值對,填充到領域模型執行個體中并傳回

2.使用場景

       在項目的實際開發中,有可能會遇到這樣兩種情況。

1.   實體類中的屬性名與列名不相同,不能改但。導緻不能自動裝配值

2.   多表查詢的傳回值中可能需要其他對象,或者數組(一對一和一對多)

二、resultMap标簽解釋

标簽及屬性介紹

<resultMap > 标簽:

      id屬性 ,resultMap标簽的辨別。

          type屬性 ,傳回值的全限定類名,或類型别名。

     autoMapping屬性 ,值範圍true(預設值)|false, 設定是否啟動自動映射功能,自動映射功能就是自動查找與字段名小寫同名的屬性名,并調用setter方法。而設定為false後,則需要在`resultMap`内明确注明映射關系才會調用對應的setter方法。

 <resultMap>可以設定的子标簽映射:

       1).id标簽  :ID 結果,将結果集标記為ID,以友善全局調用(适用于指定主鍵)

column 資料庫的列名

Property需要裝配的屬性名

      2).result标簽:将查詢到的列的值,反射到指定的JavaBean的 屬性上

column 資料庫的列名

Property 需要裝配的屬性名

      3).association标簽:複雜類型  , 多表查詢(一對一)時,将根據外鍵或某列資訊查詢出的對象,直接裝配給某個resultMap指定的屬性。

column 資料庫的列名

Property 需要裝配的屬性名

select  指定用來多表查詢的sqlmapper

      4).Collection标簽:複雜類型,多表查詢(一對多),将查詢出的結果集合直接裝配給某個對應的集合

column 資料庫的列名

Property 需要裝配的屬性名

javaType 指定用什麼類型接受傳回的值(必要)

select  指定用來多表查詢的sqlmapper

      5).constructor– 用來将結果反射給一個執行個體化好的類的構造器

a) idArg –ID 參數;将結果集标記為ID,以友善全局調用

b) arg –反射到構造器的通常結果

      6).discriminator – 使用一個結果值以決定使用哪個resultMap

a) case – 基本一些值的結果映射的case 情形

i. nestedresult mappings –一個case 情形本身就是一個結果映射,是以也可以包括一些相同的元素,也可以引用一個外部resultMap。

<resultMap type="" id="">

<id column="" property=""/>

<result column="" property="" />

       <association property="" column="" select=""></association>

       <collection property="" column="" javaType="" select=""></collection>

       <constructor></constructor>

    </resultMap>

三、各标簽使用

1.id、result

       id、result是最簡單的映射,id為主鍵映射;result其他基本資料庫表字段到實體類屬性的映射。

實體字段                    表的列名     

sid                            stuid

sname                      stuname

gid                            gid

grade                       grade  

<resultMap type="student" id="studentMap" autoMapping="true">

    <!-- 主鍵建議用id标簽   -->

    <id column="stuid" property="sid"/>

    <!-- 其他列可以用result标簽 -->

    <result column="stuname" property="sname"/>

</resultMap>

id、result語句屬性配置細節:

屬性 描述
property 需要映射到JavaBean 的屬性名稱。
column 資料表的列名或者标簽别名。
javaType 一個完整的類名,或者是一個類型别名。如果你比對的是一個JavaBean,那MyBatis 通常會自行檢測到。然後,如果你是要映射到一個HashMap,那你需要指定javaType 要達到的目的。
jdbcType 資料表支援的類型清單。這個屬性隻在insert,update 或delete 的時候針對允許空的列有用。JDBC 需要這項,但MyBatis 不需要。如果你是直接針對JDBC 編碼,且有允許空的列,而你要指定這項。
typeHandler 使用這個屬性可以覆寫類型處理器。這項值可以是一個完整的類名,也可以是一個類型别名。

支援的JDBC類型

       為了将來的引用,MyBatis支援下列JDBC 類型,通過JdbcType 枚舉:

BIT,FLOAT,CHAR,TIMESTAMP,OTHER,UNDEFINED,TINYINT,REAL,VARCHAR,BINARY,BLOB,NVARCHAR,SMALLINT,DOUBLE,LONGVARCHAR,VARBINARY,CLOB,NCHAR,INTEGER,NUMERIC,DATE,LONGVARBINARY,BOOLEAN,NCLOB,BIGINT,DECIMAL,TIME,NULL,CURSOR

2.association聯合

聯合元素用來處理“一對一”的關系。

需要指定映射的Java實體類的屬性,屬性的javaType(通常MyBatis 自己會識别)。對應的資料庫表的列名稱。如果想覆寫的話傳回結果的值,需要指定typeHandler。

不同情況需要告訴MyBatis 如何加載一個聯合。MyBatis 可以用兩種方式加載:

1. select: 執行一個其它映射的SQL 語句傳回一個Java實體類型。較靈活;

2. resultsMap: 使用一個嵌套的結果映射來處理通過join查詢結果集,映射成Java實體類型。

  association 标簽

    property=多表查詢裝配的屬性名

    column=通過那一列關聯

    select=指定查詢語句,如果查詢語句在其他的namespace中,則需要寫全namespace.方法id

例如:在擷取某個學生的資訊的同時又想擷取所屬班級的所有資訊

學生實體中的字段

package cn.et.fuqiang.resultMap.entity;

public class Student {

     private Integer sid;//學生id

     private String sname; //學生姓名

     private Integer gid; //班級id

     private Grade grade; //所屬班級的所有資訊

}

班級實體的字段

package cn.et.fuqiang.resultMap.entity;

import java.util.List;

public class Grade {

    private Integer id;//班級id

    private String gname;//班級名稱

       public Grade() {

       super();

       // TODO Auto-generated constructor stub

    }

}

在Student的字段中,Grade班級實體,需要查詢班級表才能擷取,學生表中沒有該列(是以需要使用多表查詢中的 association标簽)

想要使用多表查詢先要定義一個resultMap,簡單來說其實相當于一個工廠,可以指定某個實體中那些屬性可以裝哪一列的值,并且還要定義類似于子查詢的方法。

<!-- 使用resultMap 多表查詢 -->

<resultMap type="student" id="gradeInStudent">

    <!-- 主鍵建議用id标簽   -->

    <id column="stuid" property="sid"/>

    <!-- 其他列可以用result标簽 -->

    <result column="stuname" property="sname"/>

    <!-- 多對一  多表查詢

        傳回一個班級實體-->

    <association property="grade" column="gid" select="cn.et.fuqiang.resultMap.xml.GradeXmlInterface.queryGradeById"></association>

</resultMap>

在namespace="cn.et.fuqiang.resultMap.xml.GradeXmlInterface下的方法

<select id="queryGradeById" resultMap="gradeMap">

       select * from grade where gid=#{0}

</select>

在這個resultMap中

type:是上面定義的學生類(使用了别名,如果沒有别名可以寫全類名)

id:是這個resultMap的辨別

因為表中的字段與實體中的不同,是以使用了下面兩個子标簽,将表的列與實體的屬性做了映射。這樣才能将表中的值擷取到實體的屬性中。

<!-- 主鍵建議用id标簽   -->

<id column="stuid" property="sid"/>

<!-- 其他列可以用result标簽 -->

<result column="stuname" property="sname"/>

因為實體中還要一個班級實體,但表中沒有該字段,是以就要用多表查詢

<!-- 多對一 多表查詢傳回一個班級實體-->

<association property="grade"column="gid" select="cn.et.fuqiang.resultMap.xml.GradeXmlInterface.queryGradeById"></association>

(這裡的property是實體中的字段,而column=gid就相當于關聯的條件了

而select就是該關聯條件對應的查詢方法)

主sql

在這裡我們使用resultMap定義傳回的類型,這樣就會自動将所有内容,裝到對應的屬性上

然後直接使用該主sql,查詢,擷取的值就會自動對應到其中。

<select id="quserAllStudent" resultMap="gradeInStudent">

   select * from student

</select>

以上代碼寫為标準的sql語句

select  s.*,g.*from student s inner join grade g ons.gid=g.gid

3.  collection聚集

聚集元素用來處理“一對多”的關系。需要指定映射的Java實體類的屬性,屬性的javaType(一般為ArrayList);清單中對象的類型ofType(Java實體類);對應的資料庫表的列名稱;

不同情況需要告訴MyBatis 如何加載一個聚集。MyBatis 可以用兩種方式加載:

1. select:執行一個其它映射的SQL 語句傳回一個Java實體類型。較靈活;

2. resultsMap: 使用一個嵌套的結果映射來處理通過join查詢結果集,映射成Java實體類型。

Collection标簽

    property=多表查詢裝配的屬性名

    column=通過那一列關聯

    select=指定查詢語句,如果查詢語句在其他的namespace中,則需要寫全namespace.方法id

    javaType=java實體中接受的集合類型

例如,一個班級有多個學生。

學生實體中的字段

package cn.et.fuqiang.resultMap.entity;

public class Student {

     private Integer sid;//學生id

     private String sname; //學生姓名

     private Integer gid; //班級id

}

班級實體的字段

package cn.et.fuqiang.resultMap.entity;

import java.util.List;

public class Grade {

    private Integer id;//班級id

    private String gname;//班級名稱

      private List<Student> students;//該班級下所有的學生

    public Grade() {

       super();

       // TODO Auto-generated constructor stub

    }

}

在Grade實體中有個List<Student> students屬性,需要查詢學生表才能擷取,班級表中沒有該資訊(是以要使用collection聚集查詢對應的結果集)

<!-- 使用resultMap 多表查詢 -->

    <resultMap type="grade" id="studentsInGrade">

        <!-- 主鍵适合用 id 标簽 -->

        <id column="gid" property="id" />

        <!-- 一對多     多表查詢     collection 可擷取一個集合

            property=多表查詢裝配的屬性名

            column=通過那一列關聯

            select=查詢語句

            javaType=java實體中接受的集合類型

        -->

    <collection property="students" column="gid" javaType="list" select="cn.et.fuqiang.resultMap.xml.StudentXmlInterface.quserUserByGid"></collection>

    </resultMap>

在namespace="cn.et.fuqiang.resultMap.xml.StudentXmlInterface"下的查詢語句和類型

<resultMap type="student" id="studentMap" autoMapping="true">

         <!-- 主鍵建議用id标簽   -->

         <id column="stuid" property="sid"/>

         <!-- 其他列可以用result标簽 -->

         <result column="stuname" property="sname"/>

</resultMap>

     <!-- 使用多表查詢班級   

    注意: resultMap使用時應注意,如果該resultMap中配置了多表查詢,如果是互相查詢可能造成死循環

       -->

     <select id="quserUserByGid" resultMap="studentMap">

         select * from student where gid=#{0}

     </select>

注意:因為執行個體中學生的字段名與表中的不同是以,又加了一個學生查詢專屬的resultMap

在這個resultMap中

type:是上面定義的學生類(使用了别名,如果沒有别名可以寫全類名)

id:是這個resultMap的辨別

因為表中的字段與實體中的不同,是以使用了下面的子标簽,将表的列與實體的屬性做了映射。這樣才能将表中的值擷取到實體的屬性中。

<!-- 主鍵适合用 id 标簽 -->

<id column="gid" property="id"/>

使用多表查詢擷取某個班級下的所有學生

<collection property="students" column="gid"javaType="list" select="cn.et.fuqiang.resultMap.xml.StudentXmlInterface.quserUserByGid"></collection>

     </resultMap>

(這裡的property是實體中的字段,而column=gid就相當于關聯的條件了

而select就是該關聯條件對應的查詢方法)

主sql

<select id="queryAllGrade" resultMap="studentsInGrade">

      select * from grade

</select>

執行該映射時就會自動根據resultMap自動調用其中内嵌的sql語句查詢所有學生

所有代碼執行個體

Mybatis配置

mybatis.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>

    <!-- 指定jdbc的配置檔案位置 -->

    <properties resource="cn/et/fuqiang/resultMap/jdbc.properties"></properties>

    <!-- 配置類别名 -->

    <typeAliases>

    <!-- 使用該标簽将該包下的所有類統一起别名   預設為類名首字母小寫           -->

       <package name="cn.et.fuqiang.resultMap.entity"/>

    </typeAliases>

    <!-- 配置jdbc環境 -->

    <environments default="development">

       <environment id="development">

           <transactionManager type="JDBC" />

           <!-- 配置資料庫連接配接資訊 -->

           <dataSource type="POOLED">

              <property name="driver" value="${driverClass}" />

              <property name="url" value="${url}" />

              <property name="username" value="${user}" />

              <property name="password" value="${password}" />

           </dataSource>

       </environment>

    </environments>

    <!-- 使用接口映射  配置查詢語句 -->

    <mappers>

    <!-- 注冊接口   如果使用的是注解則不需要mappers檔案-->

       <!-- 有兩種方式配置mapper

       第一種  如果有mapper.xml檔案直接配置該檔案

       <mapper resource="mapper 檔案的類路徑">

       第二種  配置接口的全類名

       <mapper class="接口的全類名">

       -->

       <mapper class="cn.et.fuqiang.resultMap.xml.StudentXmlInterface"/>

       <mapper class="cn.et.fuqiang.resultMap.xml.GradeXmlInterface"/>

    </mappers>

</configuration>

Jdbc連接配接四要素配置

jdbc.properties

url=jdbc:oracle:thin:@localhost:1521:orcl

user=mybatis

password=mybatis

driverClass=oracle.jdbc.OracleDriver

兩個實體

student實體

package cn.et.fuqiang.resultMap.entity;

public class Student {

    private Integer sid;//學生id

    private String sname;//學生姓名

    private Integer gid;//班級id

    private Grade grade;//所屬班級的所有資訊

    public Student() {

        super();

        // TODO Auto-generated constructor stub

    }

    public Student(Integer sid, String sname, Integer gid, Grade grade) {

        super();

        this.sid = sid;

        this.sname = sname;

        this.gid = gid;

        this.grade = grade;

    }

    public Integer getSid() {

        return sid;

    }

    public void setSid(Integer sid) {

        this.sid = sid;

    }

    public String getSname() {

        return sname;

    }

    public void setSname(String sname) {

        this.sname = sname;

    }

    public Integer getGid() {

        return gid;

    }

    public void setGid(Integer gid) {

        this.gid = gid;

    }

    public Grade getGrade() {

        return grade;

    }

    public void setGrade(Grade grade) {

        this.grade = grade;

    }

}

Grade實體

package cn.et.fuqiang.resultMap.entity;

import java.util.List;

public class Grade {

    private Integer id;//班級

    private String gname;//班級名稱

    private List<Student> students;//該班級下所有的學生

    public Grade() {

        super();

        // TODO Auto-generated constructor stub

    }

    public Grade(Integer id, String gname) {

        super();

        this.id = id;

        this.gname = gname;

    }

    public String getGname() {

        return gname;

    }

    public void setGname(String gname) {

        this.gname = gname;

    }

    public Integer getId() {

        return id;

    }

    public void setId(Integer id) {

        this.id = id;

    }

    public List<Student> getStudents() {

        return students;

    }

    public void setStudents(List<Student> students) {

        this.students = students;

    }

}

兩個接口映射的接口

GradeXmlInterface.java

package cn.et.fuqiang.resultMap.xml;

import java.util.List;

import cn.et.fuqiang.resultMap.entity.Grade;

public interface GradeXmlInterface {

    public Grade queryGradeById(Integer id);

    public List<Grade> queryAllGrade();

}

StudentXmlInterface.java

package cn.et.fuqiang.resultMap.xml;

import java.util.List;

import cn.et.fuqiang.resultMap.entity.Student;

public interface StudentXmlInterface {

    public Student queryStudentById(Integer sid);

    public List<Student> quserAllStudent();

    public List<Student> quserUserByGid();

}

兩個映射的mapper.xml

GradeXmlInterface.xml

<?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="cn.et.fuqiang.resultMap.xml.GradeXmlInterface">

    <!-- 當實體的屬性名與表名不一緻時可使用 resultMap标簽指定對應的列值給對應的屬性

       autoMapping是設定是否自動裝配屬性名與表的列名對應的變量

    -->

    <resultMap type="grade" id="gradeMap" autoMapping="false">

       <!-- 主鍵适合用 id 标簽 -->

       <id column="gid" property="id"/>

       <!-- 其他的用result标簽 -->

       <result column="gname" property="gname"/>

    </resultMap>

    <select id="queryGradeById" resultMap="gradeMap">

       select * from grade where gid=#{0}

    </select>

    <!-- 使用resultMap 多表查詢 -->

    <resultMap type="grade" id="studentsInGrade">

       <!-- 主鍵适合用 id 标簽 -->

       <id column="gid" property="id" />

       <!-- 一對多     多表查詢     collection 可擷取一個集合

           property=多表查詢裝配的屬性名

           column=通過那一列關聯

           select=查詢語句

           javaType=java實體中接受的集合類型

       -->

       <collection property="students" column="gid" javaType="list" select="cn.et.fuqiang.resultMap.xml.StudentXmlInterface.quserUserByGid"></collection>

    </resultMap>

       <!-- 使用多表查詢在查詢班級的同時根據上面的resultMap查出對應的所有學生 

       注意: resultMap使用時應注意,如果該resultMap中配置了多表查詢,如果是互相查詢可能造成死循環

      -->

    <select id="queryAllGrade" resultMap="studentsInGrade">

       select * from grade

    </select>

</mapper>

StudentXmlInterface.xml

<?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="cn.et.fuqiang.resultMap.xml.StudentXmlInterface">

    <!-- 當實體的屬性名與表名不一緻時可使用 resultMap标簽指定對應的列值給對應的屬性

       autoMapping是設定是否自動裝配屬性名與表的列名對應的變量(不寫預設是true)

    -->

    <resultMap type="student" id="studentMap" autoMapping="true">

       <!-- 主鍵建議用id标簽   -->

       <id column="stuid" property="sid"/>

       <!-- 其他列可以用result标簽 -->

       <result column="stuname" property="sname"/>

    </resultMap>

    <select id="queryStudentById" resultMap="studentMap">

       select * from student where stuid=#{0}

    </select>

    <!-- 使用resultMap 多表查詢 -->

    <resultMap type="student" id="gradeInStudent">

       <!-- 主鍵建議用id标簽   -->

       <id column="stuid" property="sid"/>

       <!-- 其他列可以用result标簽 -->

       <result column="stuname" property="sname"/>

       <!-- 多對一     多表查詢     association

           property=多表查詢裝配的屬性名

           column=通過那一列關聯

           select=查詢語句

       -->

       <association property="grade" column="gid" select="cn.et.fuqiang.resultMap.xml.GradeXmlInterface.queryGradeById"></association>

    </resultMap>

    <select id="quserAllStudent" resultMap="gradeInStudent">

       select * from student

    </select>

    <!-- 使用多表查詢班級   

       注意: resultMap使用時應注意,如果該resultMap中配置了多表查詢,如果是互相查詢可能造成死循環

      -->

    <select id="quserUserByGid" resultMap="studentMap">

       select * from student where gid=#{0}

    </select>

</mapper>

測試類

package cn.et.fuqiang.resultMap.xml;

import java.io.InputStream;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.junit.Test;

import cn.et.fuqiang.resultMap.entity.Grade;

import cn.et.fuqiang.resultMap.entity.Student;

public class ResultMapXmlTest {

   private static SqlSession session;

   private static GradeXmlInterface gradeInter=null;

   private static StudentXmlInterface studentInter=null;

   static{

       //mybatis的配置檔案

       String resource = "mybatis.xml";

       //使用類加載器加載mybatis的配置檔案(它也加載關聯的映射檔案)

       InputStream is =GradeXmlInterface.class.getResourceAsStream(resource);

       //建構sqlSession的工廠

       SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

       //使用MyBatis提供的Resources類加載mybatis的配置檔案(它也加載關聯的映射檔案)

       //Readerreader = Resources.getResourceAsReader(resource);

       //建構sqlSession的工廠

       //SqlSessionFactorysessionFactory = new SqlSessionFactoryBuilder().build(reader);

       //建立能執行映射檔案中sql的sqlSession

       session = sessionFactory.openSession();

       gradeInter=session.getMapper(GradeXmlInterface.class);

       studentInter=session.getMapper(StudentXmlInterface.class);

   }

   //@Test

   public void queryGradeTest(){

       Grade grade=gradeInter.queryGradeById(1);

       System.out.println(grade.getId()+"------"+grade.getGname());

   }

   //@Test

   public void queryStudentTest(){

       Student student=studentInter.queryStudentById(1);

       System.out.println(student.getSid()+"------"+student.getSname());

   }

   //@Test

   public void queryGradeInStudent(){

       List<Student> students=studentInter.quserAllStudent();

       for (Student stu : students) {

            System.out.println(stu.getSid()+"------"+stu.getSname()+"-------"+stu.getGrade().getGname());

       }

   }

   @Test

   public void queryStudentInGrade(){

       List<Grade> students=gradeInter.queryAllGrade();

       for (Grade grade : students) {

            System.out.println(grade.getGname()+"班"+grade.getStudents().size()+"人");

       }

   }

}

繼續閱讀