1. 程式人生 > 實用技巧 >Mybatis學習02--XML對映器(1)

Mybatis學習02--XML對映器(1)

XML對映器

從上一篇的第一個程式可以看到,我們只需要在對映器Mapper.xml中新增SQL程式碼和對映定義,Mybatis就會自動將查詢對映到介面方法上。

引數注入

根據name和pwd查詢使用者,SQL程式碼如下:

select * from user where name=? and pwd=?;

如果在Java類中屬性名與資料庫列名一致,直接使用#{param name}代替?即可,Mybatis能夠匹配引數的名字,但不一致的情況下,可以用以下方法完成對映:

使用@Param註解
public User selectByNamePwd(@Param("username")String name,@Param("password")String pwd);
<select id="selectByNamePwd" resultType="com.tang.User">
        select * from user
        where name=#{username} and pwd=#{password}
</select>

注意引數名要與#{}中的名字一致

使用Map
 public User selectByNamePwd2(Map<String,Object> map);
<select id="selectByNamePwd2" parameterType="map" resultType="com.tang.User">
        select * from user
        where name=#{username} and pwd=#{password}
</select>
Map<String,Object> map=new HashMap<String,Object>();
map.put("password","12345");
map.put("username","Su");
System.out.println(mapper.selectByNamePwd2(map));

注意在map中放入的key字串與#{}中一致

字串替換

${}和#{}的區別

'#{}中的值作為字串處理,會加引號;${}中的值不作處理,直接替換sql語句中的佔位符。$常用於資料庫物件,如表名,欄位,例如:'

'#{}在預編譯時會用?代替引數'

${}在動態解析

時直接替換字串,會引起sql注入問題

如下面的例子,利用${}可以使用一個方法可以實現3個方法的功能,只需要定義一個介面方法 selectByColumn 而不是3個方法

selectById(int id);
selectByName(String name);
selectByPwd(String pwd);
@Select("select * from user where ${column}=#{value}")
public User selectByColumn(@Param("column")String col,@Param("value")Object val);
System.out.println(mapper.selectByColumn("id",2));      
System.out.println(mapper.selectByColumn("name","Su"));
System.out.println(mapper.selectByColumn("pwd","abcd"));

結果對映(ResultMap)

在Mapper.xml檔案中,沒有顯式指定ResultMap的情況下,Sql語句的查詢結果會簡單地對映到HashMap上,HashMap的key由資料庫列名決定,例如以下的查詢

@Select("select * from department")
public List<Department> selectDepartment();

實體類Department:

public class Department {
    private String name;
    private int id;
    private int mid;
}

在資料庫中建表如下:

查詢結果對映到HashMap中,會存放如下的鍵值對:

<"did",1> ,<"dname","HR">, <"mid", "2"> …………

mybatis根據key查詢實體類User中的set(setDid,setDname,setMid)方法,由此帶來的問題是,如果實體類的屬性名與資料庫列名不一致,那麼set方法無法找到,最終的查詢結果返回User物件,物件中的屬性無法獲得正確的值。查詢結果如下:

depart name=null id=0 manager id=2
depart name=null id=0 manager id=1
depart name=null id=0 manager id=3

解決辦法:使用ResultMap

<resultMap id="DepartmentMap" type="Department">
    <!--id為主鍵-->
    <id column="did" property="id"/>
    <!-- column 為資料庫中列名,property 為實體類屬性名 -->
    <result column="dname" property="name"/>
    <result column="mid" property="mid"/>
</resultMap>
    <!-- resultMap 與前面的id對應 -->
<select id="selectDepartment2" resultMap="DepartmentMap">
    select * from department
</select>

ResultMap有很多子元素

id&result

id和result都將資料庫一列對映到實體類的屬性或欄位上,id元素對應的屬性會被標記為物件識別符號,在比較物件例項中使用。

constructor

將查詢結果注入到類的構造方法中,例如

 public User(Integer id, String username, int age) {
     //...
  }
<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>

JavaType指定了引數型別,例中分別是Java.lang.Integer, int, Java.lang.String。name指定了引數名稱,在構造方法中要使用@Param註解對應,如果引數順序一致,可以省略name。

association

關聯(association)元素處理“有一個”型別的關係。mybatis有兩種方式載入關聯:

  • 巢狀查詢
  • 巢狀結果對映

接下來看一個例子,用上面的兩種方法實現。

資料庫建表:

根據部門id查詢部門及其主管資訊,巢狀查詢的SQL實現如下:

實體類:

@Data
public class Department {
    private String name;
    private int id;
    private Employee manager;
    private List<Employee> employees;
}
@Getter
@Setter
public class Employee {
    private String name;
    private int id;
    private Department department;
    public String toString(){
        return "Employee:name="+name+" id="+id+"department"+department.getName();
    }
}

在DepartmentMapper介面中新增查詢方法:

public Department selectDepartment(int did);

用mybatis實現:

<!-- 先查詢部門資訊,再根據mid查詢主管資訊 -->
<resultMap id="DepartmentManager" type="Department">
    <!-- property:在實體類中屬性名 column{key=value,key=value}
    key傳給sql查詢的取值,value查詢結果在資料庫中的欄位名
    javaType java類,select 巢狀查詢中內層查詢的名字,與下面select id對應 -->
    <id column="did" property="id"/>
    <result column="dname" property="name"/>
    <association property="manager" column="mid"
                 javaType="Employee" select="selectManager"/>
</resultMap>

<select id="selectDepartment" resultMap="DepartmentManager">
    select * from department where did=#{did}
</select>
<select id="selectManager" resultMap="Employee">
    select * from employee where id=#{id}
</select>

出現的問題

Error attempting to get column 'ename' from result set.  Cause: java.sql.SQLException: Invalid value for getInt() - 'Su'

分析:資料庫列名'ename'與實體屬性名‘name’不匹配,而且資料庫中的順序是(id,ename,did),實體類中是(name,id,department)

用resultMap

<resultMap id="EmployeeMap" type="Employee">
        <!--constructor>
            <arg column="id" javaType="_int" name="id"/>
            <arg column="ename" javaType="String" name="name"/>
        </constructor-->
        <id column="id" property="id"/>
        <result column="ename" property="name"/>
</resultMap>
<select id="selectManager" resultMap="EmployeeMap">
    select * from employee where id=#{id}
</select>

仍然報錯,新增無參建構函式,或者使用constructor並增加對應構造方法

 <resultMap id="EmployeeMap" type="Employee">
        <constructor>
            <arg column="id" javaType="_int" name="id"/>
            <arg column="ename" javaType="String" name="name"/>
        </constructor>
        <!--id column="id" property="id"/>
        <result column="ename" property="name"/-->
</resultMap>

在Employee中增加構造方法和註解:

public Employee(@Param("name") String name, @Param("id") int id){
        //System.out.println("constructor");
        this.name=name;
        this.id=id;
}

執行結果:

接下來來看另一種實現方法:結果對映

連線查詢的SQL語句實現如下:

mybatis實現

    <select id="selectDepartment2" resultMap="DepartmentMap">
        select d.did as did,d.dname,mid,ename from department d,employee e
        where d.did=#{did} and d.mid=e.id
    </select>
    <resultMap id="DepartmentMap" type="Department">
        <result property="id" column="did"/>
        <result property="name" column="dname"/>
        <!-- 關聯Department類中的成員:Employee manager -->
        <association property="manager" javaType="Employee">
            <result column="mid" property="id"/>
            <result column="ename" property="name"/>
        </association>
    </resultMap>

出現的問題:

可以看到dname被當成了did進行處理,按照網上的說法,要給Java類Department新增無參建構函式和Set()方法,但我已經添加了,仍然報錯,最後發現是引數順序的問題,在Java類中是(name, id, manager),所以要改一下SQL語句,dname在前,did在後:

 select d.dname,d.did as did,mid,ename from department d,employee e
        where d.did=#{did} and d.mid=e.id

執行結果:

集合的對映

上面的例子是一對一關係的對映(一個部門只有一個主管),下面來看一對多關係的對映如何處理:

SQL語句查詢:查詢部門1的所有員工

mybatis實現:使用collection元素

<select id="selectDepartment3" resultMap="DepartmentMap2">
    select dname,d.did,e.id as eid,ename from department d,employee e
    where d.did=#{did} and d.did=e.did
</select>
<resultMap id="DepartmentMap2" type="Department">
    <result property="id" column="did"/>
    <result property="name" column="dname"/>
    <!-- ofType:集合中元素型別 -->
    <collection property="employees" ofType="Employee">
        <result column="eid" property="id"/>
        <result column="ename" property="name"/>
    </collection>
</resultMap>

執行結果