2.2、MyBatis的XML基本用法——select用法

select用法

使用纯粹的JDBC时,需要写查询语句,并且对结果集进行手动处理,将结果映射到对象的属性中

使用 MyBatis 时,只需要在XML中添加一个select元素,写一个SQL,再做一些简单的配置,就可以将查询的结果直接映射到对象中

下面以用id查找用户为例,讲解select用法

1、添加对应接口方法selectById

public interface UserMapper {

    /**
     * 通过 id 查询用户
     * 
     * @param id
     * @return
     */
    SysUser selectById(Long id);
}

2、添加对应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="tk.mybatis.simple.mapper.UserMapper">
    <resultMap id="userMap" type="tk.mybatis.simple.model.SysUser">
        <id property="id" column="id" />
        <result property="userName" column="user_name" />
        <result property="userPassword" column="user_password" />
        <result property="userEmail" column="user_email" />
        <result property="userInfo" column="user_info" />
        <result property="headImg" column="head_img" jdbcType="BLOB" />
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
    </resultMap>

    <select id="selectById" resultMap="userMap">
        select * from sys_user where
        id = #{id}
    </select>
</mapper>

接口与XML的关联:通过XML的<mapper>标签的namespace值设置为接口的全限定名称进行关联

接口中方法与XML的关联:通过XML的<select>标签的id值设置为接口方法名称进行关联

3、XML的设计规则:

a、只使用XML而不使用接口时,namespace的值可以设置为任意不重复的值

b、标签的 id 属性值在任何时候都不能出现英文句号 “.” ,并且同一个命名空间下不能出现重复的id

c、因为接口方法是可以重载的,所以接口中可以出现多个同名但参数不同的方法,但是 XML 中 id 的值不能重复,因而接口中的所有同名方法会对应着 XML 中的同一个 id 的方法。最常见的用法就是,同名方法中其中一个方法增加一个 RowBound  类型的参数用于实现分页查询

4、XML的标签与属性

<select>标签:映射查询语句使用的标签
id:命名空间中的唯一标识符,可用来代表这条语句
resultMap:用于设置返回值的类型和映射关系
resultType:直接自动影射为某对象
select * from sys_user where id=#{id} 是查询语句
#{id}:MyBatis SQL中使用预编译参数的一种方式,大括号中的 id 是传入的参数名
在上面的select中,使用resultMap设置返回值的类型,这里的userMap就是上面<resultMap>中的id属性值,通过id引用需要的<resultMap>
<resultMap>标签:用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到type对象的属性上,因此当我们使用select*查询所有列的时候,MyBatis也可以将结果正确地映射到SysUser对象上
id:必填,并且唯一。在select标签中,resultMap指定的值即为此处id所设置的值
type:必填,用于配置查询列所映射到的Java对象类型
extends:选填,可以配置当前的resultMap继承自其他的resultMap,属性值为继承resultMap的id
autoMapping:选填,可选值为true或false,用于是否启用非映射字段(没有在resultMap中的字段)的自动映射功能,该可以覆盖全局的autoMappingBehavior
<resultMap>下的标签:
<constructor>标签:配置使用构造方法注入结果,包含以下两个子标签
  <idArg>标签:id参数,标记结果作为id(唯一值),可以帮助提高整体性能。
  <arg>标签:注入到构造方法的一个普通结果。
<id>标签:一个id结果,标记结果作为id(唯一值),可以帮助提高整体性能。
<result>标签:注入到Java对象属性的普通结果。
<idArg>、<arg>和<id>、<result>的含义相同,不同在于前者是通过类的构造函数注入,后者是通过属性的setter方法注入
<association>标签:一个复杂的类型关联,许多结果将包成这种类型。
<collection>标签:复杂类型的集合。
<discriminator>标签:根据结果值来决定使用哪个结果映射。
<case>标签:基于某些值的结果映射。
<id>和<result>里面的属性
column:从数据库中得到的列名,或者是列的别名。
property:映射到列结果的属性。可以映射简单的如“username”这样的属性,也可以映射一些复杂对象中的属性,例如“address.street.number”,这会通过“.”方式的属性嵌套赋值。
javaType:一个Java类的完全限定名,或一个类型别名(通过typeAlias配置或者默认的类型)。如果映射到一个JavaBean,MyBatis通常可以自动判断属性的类型。如果映射到HashMap,则需要明确地指定javaType属性。
jdbcType:列对应的数据库类型。JDBC类型仅仅需要对插入、更新、删除操作可能为空的列进行处理。这是JDBCjdbcType的需要,而不是MyBatis的需要。
typeHandler:使用这个属性可以覆盖默认的类型处理器。这个属性值是类的完全限定名或类型别名
如何定义返回值
resultMap:用resultMap来设置映射
resultType:根据读取结果和对应类自动映射,可以在select中使用别名,来和类的属性名对应

5、多结果查询例子

接口方法

/**
     * 查询全部用户
     * 
     * @return
     */
    List<SysUser> selectAll();

XML设置

<select id="selectAll" resultType="tk.mybatis.simple.model.SysUser">
    select id,
    user_name userName,
    user_password userPassword,
    user_email userEmail,
    user_info userInfo,
    head_img headImg,
    create_time createTime
    from sys_user
</select>

这个例子中,select标签中直接使用resultType,以及使用字段别名,使sql的字段名与类的字段名对上,即可自动映射

6、名称映射规则

综上,有2种方式:1、在resultMap中配置property属性和column属性的映射;2、SQL中设置别名

property或者别名要与对象的属性名对应才能匹配,实际匹配时,是把字母都转换成大写来匹配的,所以不区分大小写;一般为了阅读,应该统一写法就OK

一种很常见的情况,数据库使用下划线命名方式,如user_Name;而Java中使用驼峰式命名,如userName

MyBatis提供了一个配置,自动将这2种方式进行匹配,在配置文件中设置即可,代码如下

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

7、添加单元测试代码

在src/test/java下的tk.mybatis.simple.mapper包中,添加基础测试类

package tk.mybatis.simple.mapper;

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.BeforeClass;

/**
 * 基础测试类
 */
public class BaseMapperTest {
    private static SqlSessionFactory sqlSessionFactory;
    
    @BeforeClass
    public static void init(){
        try {
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            reader.close();
        } catch (IOException ignore) {
            ignore.printStackTrace();
        }
    }
    
    public SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
    
}

修改CountryMapperTest类

因为Country和User的Mapper中都有SelectAll,所以不在唯一,需要用全限定类名tk.mybatis.simple.mapper.CountryMapper.selectAll去调用

package tk.mybatis.simple.mapper;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import tk.mybatis.simple.model.Country;

public class CountryMapperTest extends BaseMapperTest {

    @Test
    public void testSelectAll() {
        SqlSession sqlSession = getSqlSession();
        try {
            List<Country> countryList = sqlSession.selectList("tk.mybatis.simple.mapper.CountryMapper.selectAll");
            printCountryList(countryList);
        } finally {
            sqlSession.close();
        }
    }

    private void printCountryList(List<Country> countryList) {
        for (Country country : countryList) {
            System.out.printf("%-4d%4s%4s\n", country.getId(), country.getCountryname(), country.getCountrycode());
        }
    }
}

 修改mybatis-config.xml,把CountryMapper.xml加入到Mapper配置中

<mappers>
    <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml" />
    <package name="tk.mybatis.simple.mapper" />
</mappers>

完成上面设置后即可进行单元测试

8、一些复杂用法

a、关联查找某个表的数据

接口方法

/**
 * 根据用户 id 获取角色信息
 * 
 * @param userId
 * @return
 */
List<SysRole> selectRolesByUserId(Long userId);

XML配置

<select id="selectRolesByUserId" resultType="tk.mybatis.simple.model.SysRole">
    select 
        r.id, 
        r.role_name roleName, 
        r.enabled,
        r.create_by createBy,
        r.create_time createTime,
        u.user_name as "user.userName",
        u.user_email as "user.userEmail"
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
    where u.id = #{userId}
</select>

因为使用了自定义类型Enable,所以在mybatis-config.xml加入自定义类型处理器

<typeHandlers>
    <typeHandler 
        javaType="tk.mybatis.simple.type.Enabled" 
        handler="tk.mybatis.simple.type.EnabledTypeHandler"/>
</typeHandlers>

这种情况,虽然有关联表查询,但是只是一个实体的数据,所以只是sql中加入了join语句的不同,其它和单个表查询基本相同

b、关联查询多个表数据

例如a中,我不仅要查询角色,也要带上用户的信息,那就是要查询出2个表的信息了,那么可以如下这么做

第一种:把用户的信息增加到角色表中;不过这种方式不建议

package tk.mybatis.simple.model;

public class SysRoleExtend extends SysRole {
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

}

第二种:把user表加到角色表中

public class SysRole implements Serializable {
    /**
     * 用户信息
     */
    private SysUser user;

        ......

修改mapper,使用“.”的方式增加user需要的信息

<select id="selectRolesByUserId" resultType="tk.mybatis.simple.model.SysRole">
    select 
        r.id, 
        r.role_name roleName, 
        r.enabled,
        r.create_by createBy,
        r.create_time createTime,
        u.user_name as "user.userName",
        u.user_email as "user.userEmail"
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
    where u.id = #{userId}
</select>

测试代码

@Test
    public void testSelectRolesByUserId(){
        SqlSession sqlSession = getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //调用 selectRolesByUserId 方法查询用户的角色
            List<SysRole> roleList = userMapper.selectRolesByUserId(1L);
            //结果不为空
            Assert.assertNotNull(roleList);
            //角色数量大于 0 个
            Assert.assertTrue(roleList.size() > 0);
        } finally {
            //不要忘记关闭 sqlSession
            sqlSession.close();
        }
    }