Mybatis学习02--XML映射器

XML映射器

从上一篇的第一个程序可以看到,我们只需要在映射器Mapper.xml中添加SQL代码和映射定义,Mybatis就会自动将查询映射到接口方法上。
下面来看一些常用的SQL查询如何用mybatis实现:

参数注入

根据name和pwd查询用户,SQL代码如下:

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

如果在Java类中属性名与数据库列名一致,直接使用#{数据库列名}代替?即可,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
  • constructor
  • association
  • collection
id&result

id和result都将数据库一列映射到实体类的属性或字段上,id元素对应的属性会被标记为对象标识符,在比较对象实例中使用。

用法:

<id column="数据库列名" property="实体类属性名"/>
<result column="数据库列名" property="实体类属性名"/>
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

运行结果:

collection

上面的例子是一对一关系的映射(一个部门只有一个主管),下面来看一对多关系的映射如何处理:

SQL语句查询:查询部门1的所有员工

mybatis实现:使用collection元素,仍然是两种方法,嵌套查询和结果映射

  • 集合的嵌套结果映射
  • 集合的嵌套select查询

集合的嵌套结果映射

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

运行结果


集合的嵌套select查询

SQL实现如下

mybatis实现:

<select id="selectDepartment4" resultMap="DepartmentMap3">
    select * from department
    where did=#{did}
</select>
<select id="selectEmployees" resultMap="EmployeeMap">
    select * from employee
    where did=#{did}
</select>
<resultMap id="DepartmentMap3" type="Department">
    <id column="did" property="id"/>
    <result column="dname" property="name"/>
    <!-- 与association类似 将select="selectEmployees"的查询结果映射到
    Department中的List<Employee> employees上
    column:{key=value}:
    key是实体类Department的属性名 value传给查询selectEmployees的数据库列名 -->
    <collection property="employees" javaType="ArrayList"
                column="{id=did}" ofType="Employee" select="selectEmployees"/>
</resultMap>

运行结果

posted @ 2020-12-04 19:34  剑意由心  阅读(135)  评论(0)    收藏  举报