【MyBatis】 MyBatis修炼之四 MyBatis XML方式的基本用法(SELECT)

我们执行查询操作,使用MyBatis,我们只需要在XML中添加select元素,然后写上SQL语句,然后再做一些简单的配置,就可以将查询的结果直接映射到对象中。

MyBatis参考文档:

中文版:http://www.mybatis.org/mybatis-3/zh/index.html
英文版:http://www.mybatis.org/mybatis-3/

工具

JDK 1.6及以上版本
MyBatis 3.30版本
MySQL 6.3版本
Eclipse4 及以上版本
Apache Maven 构建工具


项目源码下载地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex


先写一个根据用户id,查询用户信息的简单方法。在UserMapper接口中添加一个selectById方法。

package mybatis.simple.mapper;

import mybatis.simple.model.SysUser;

public interface UserMapper {
    
    public SysUser selectById(Long id);

}

然后在对应的UserMapper.xml中添加如下的<resultMap>和<select>部分的代码。

<?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="mybatis.simple.mapper.UserMapper">

    <resultMap type="mybatis.simple.model.SysUser" id="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="SysUser">
        select * from sys_user where id = #{id}
    </select>

</mapper>

创建接口和XML时我们知道接口和XML是通过将namespace的值设置为接口的全限定名称来进行关联的,那么接口中的方法和XML又是怎么关联的呢?

XML中的select标签的id属性值和定义的接口方法名一样,MyBatis就是通过这种方式将接口方法和XML中定义的SQL语句关联到一起的。如果接口方法没有和XML中的id属性值相对应,启动程序便会报错。

映射XML和接口的命名需要符合规则如下:

① 当只使用XML而不使用接口的时候,namespace的值可以设置为任意不重复的名称。
② 标签的id属性值在任何时候都不能出现英文的句号“.”,并且同一个命名空间下不能出现重复的id
③ 因为接口方法是可以重载的,所以接口中可以出现多个同名但是参数不同的方法,但是XML中id的值不能重复,那么接口中的多有的同名方法都对应着XML中的同一个id的方法。

标签和属性的作用:

  • <select>:映射查询语句使用的标签。
  • id:命名空间中唯一标识符,可用来代表这条语句
  • resultMap:用于设置返回值得类型和映射关系。
  • select标签中的select * from sys_user where id = #{id}是查询语句。
  • #{id}:MyBatis SQL中使用预编译参数的一种方式,大括号中的id是传入的参数名。

在上面的<select>中,使用resultMap设置返回值得类型,这里的SysUser就是<resultMap>中的id属性值,通过id引用需要的<resultMap>。

resultMap标签用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到type对应的属性上,因此当我们使用select *进行查询所有列时,MyBatis也可以将结果正确地映射到SysUser对象上。

resultMap包含的所有属性:

  • 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对象属性的普通结果。
  • association:一个复杂的类型观念,许多结果将包成这种类型
  • collection:复杂类型的集合。
  • discriminator:根据结果值来决定使用哪个结果映射
  • case:基于某些值的结果映射。

constructor通过构造方法注入属性的结果值。构造方法中的idArg、arg参数分别对应着resultMap标签中的id、result标签,它们的含义相同,只是注入的方式不同。
resultMap中的id和result标签包含的属性相同,不同的地方在于id代表的是主键(或唯一键)的字段(可以有多个),它们的属性值是通过setter方法注入的。

id和result标签包含的属性:

  • column:从数据库得到的列名,或者是列的别名。
  • property:映射到列结果的属性(JavaBean的属性)
  • javaType:一个Java类的完全限定名,或一个类型别名(通过mybatis-config.xml中的typeAlias配置或者默认的类型)。如果映射到一个JavaBean,MyBatis通常可以自动判断属性的类型。
  • jdbcType:列对应的数据库类型。JDBC类型仅仅需要对插入、更新、删除操作可能为空的列进行处理。
  • typeHandler:使用这个属性可以覆盖默认的类型处理器。

接口中定义的返回值类型必须和XML中配置的resultType类型一致,否则就会因为类型不一致而抛出异常。返回值类型是由XML中的resultType(或者resultMap中的type)决定,不是由接口中写的返回值类型决定的。

UserMapper接口中的selectById方法,通过主键id查询,最多只会返回一条数据,所以这里的返回值是SysUser.

我们再写一个查询所有用户的方法,在UserMapper接口中添加selectAll方法;

package mybatis.simple.mapper;

import java.util.List;

import mybatis.simple.model.SysUser;

public interface UserMapper {
    
    public SysUser selectById(Long id);
    
    public List<SysUser> selectAll();

}

并在对应的UserMapper.xml中添加新的查询所有用户的<select>部分:

<?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="mybatis.simple.mapper.UserMapper">

    <resultMap type="mybatis.simple.model.SysUser" id="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="SysUser">
        select * from sys_user where id = #{id}
    </select>

    <select id="selectAll" resultType="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>

</mapper>

观察我们selectById和selectAll我们可以发现:
1、selectById中的返回最多只有1个结果时,我们可以直接使用JavaBean对象来接收返回数据。而当执行的SQL返回多个结果时,接口必须使用List<Object>或Object[]作为返回值(Object为具体的JavaBean对象)。
2、使用resultMap和resultType都可以返回对象集合,使用resultType限定返回类型时,属性值为返回对象的权限定类名,使用resultMap限定返回类型时,属性值为resultMap标签中定义的id的值。
3、如果使用resultType来设置返回结果的类型,需要在SQL中为所有的列名和属性名不一致的列设置别名,通过设置别名来使最终的查询结果列和resultType指定对象的属性名保持一致,进而实现自动映射。

名称映射规则:
可以在resultMap中配置property属性和column属性的映射,或者在SQL中设置别名,这两种方式实现将查询列映射到对象属性的目的。
property属性或别名要和对象中属性的名字相同,但是实际匹配时,MyBatis会先将两者都转换为大写形式,然后再判断是否相同,即property=“userName”和proeprty="username"都可以匹配到对象的userName属性上。判断是否相同的时候要使用USERNAME,因此在设置property属性或别名的时候,不需要考虑大小写是否一致。

下面通过测试来验证我们的两个查询。
首先我们提取出一个基础测试类BaseMapperTest,而其他测试类,都继承此类,通过此类提供的getSqlSession()获取SqlSession对象。
BaseMapperTest基础测试类:

package mybatis.simple.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.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);
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}

编写UserMapperTest测试类;

package mybatis.simple.test;

import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import mybatis.simple.mapper.UserMapper;
import mybatis.simple.model.SysUser;

public class UserMapperTest extends BaseMapperTest {

    @Test
    public void testSelectById(){
        //获取SqlSession
        SqlSession sqlSession = getSqlSession();
        
        //获取UserMapper接口
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //调用selectById方法,查询id=1的用户
            SysUser user = userMapper.selectById(1L);
        } finally{
            //关闭SqlSession
            sqlSession.close();
        }
    }
    
    
    @Test
    public void testSelectAll(){
        //获取SqlSession
                SqlSession sqlSession = getSqlSession();
                
                //获取UserMapper接口
                try {
                    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                    //调用selectById方法,查询id=1的用户
                    List<SysUser> userList = userMapper.selectAll();
                } finally{
                    //关闭SqlSession
                    sqlSession.close();
                }
    }
}

右键单击该类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 511473681.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - ==>  Preparing: select id, user_name userName, user_password userPassword, user_email userEmail, user_info userInfo, head_img headImg, create_time createTime from sys_user 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime
TRACE [main] - <==        Row: 1, admin, 123456, admin@mybais.alex, <<BLOB>>, <<BLOB>>, 2017-08-09 15:26:52.0
TRACE [main] - <==        Row: 2, test, 123456, test@mybais.alex, <<BLOB>>, <<BLOB>>, 2017-08-09 15:27:30.0
DEBUG [main] - <==      Total: 2
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Returned connection 511473681 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 511473681 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1, admin, 123456, admin@mybais.alex, <<BLOB>>, <<BLOB>>, 2017-08-09 15:26:52.0
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1e7c7811]
DEBUG [main] - Returned connection 511473681 to pool.

上面的两个SELECT查询仅仅是简单的单表查询,而我们在实际情况下,通常需要进行多表关联查询,关联查询的结果也会有多种情况。
第一种情况:根据用户id获取用户拥有的所有角色,返回的结果为角色集合,结果只有角色的信息,不包含额外的其他字段信息。
在UserMapper接口中添加一个方法selectRoleByUserId

public List<SysRole> selectRoleByUserId(Long userId);

并在UserMapper.xml中添加如下代码;

    <select id="selectRoleByUserId" resultType="mybatis.simple.model.SysRole">
        select r.id,
        r.role_name roleName,
        r.enabled,
        r.create_by createBy,
        r.create_time createTime
        from sys_user_role ur , sys_role r where
        ur.role_id=r.id
        and ur.user_id =
        #{userId}
    </select>

虽然这是从多张表中查询出的数据,但是结果值包含一个表(sys_role)中的信息,所以直接返回SysRole即可。

第二种情况:在第一种情况的基础下,返回的结果不仅包含角色的信息,还包含当前用户的部分信息(用户名)。
实现此种情况,我们有三种方法
1、在SysRole对象中直接添加userName属性,这样仍然使用SysRole作为返回值。

public class SysRole {
    //其他原字段
    private String userName;

        public String getUserName() {
        return userName;
    }

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

   //原字段的getter和setter
}

2、新建一个类,继承SysRole,在该类中添加属性userName,最后返回该类即可。

package mybatis.simple.model;

public class SysRoleUser extends SysRole{
    private String userName;

    public String getUserName() {
        return userName;
    }

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

3、将用户对象最为一个属性添加到角色中

public class SysRole {
    //其他原字段
    private SysUser user;

   //原字段的getter和setter
}

直接在SysRole中增加SysUser对象,字段名为user,增加这个字段后,修改XML中的selectRoleByUserId方法

<select id="selectRoleByUserId" resultType="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"
        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>

注意 u.user_name as "user.userName",这里设置别名时,使用的是“user.属性名”,user是SysRole中添加的属性,userName是SysUser对象中的属性,通过这种方式直接将值赋给user字段中的属性。

在UserMapperTest中添加测试代码:

@Test
    public void testSelectRoleByUserId() {
        // 获取SqlSession
        SqlSession sqlSession = getSqlSession();

        // 获取UserMapper接口
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 调用selectById方法,查询id=1的用户
            List<SysRole> roleList = userMapper.selectRoleByUserId(1L);
        } finally {
            // 关闭SqlSession
            sqlSession.close();
        }
    }

右键单击该类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 2011986105.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77ec78b9]
DEBUG [main] - ==>  Preparing: select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime, u.user_name as "user.userName" 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 = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, roleName, enabled, createBy, createTime, user.userName
TRACE [main] - <==        Row: 1, 管理员, 1, 1, 2017-08-09 15:26:52.0, admin
TRACE [main] - <==        Row: 2, 普通用户, 1, 1, 2017-08-09 15:26:52.0, admin
DEBUG [main] - <==      Total: 2
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77ec78b9]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@77ec78b9]
DEBUG [main] - Returned connection 2011986105 to pool.

从输出的日志中我们可以很明显的看到增加了用户名的列数据。

总结:
① SELECT语句返回的数据只为一个对象(表)的数据,可以直接使用对象接收。
② SELECT语句返回的数据为多个对象(多个表)中的字段,此时可以新建一个类,包含所有要返回的字段,然后使用该类作为返回数据类型。
③ SELECT语句返回的数据包含大量的其他对象(表)的字段,则可以将额外字段所属对象最为属性,添加到主对象中,而后在配置Mapper.xml中的SQL语句时,采用“对象.属性”的方式作为别名,将值赋给额外的字段。


项目源码下载地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex


上一篇: 【MyBatis】 MyBatis修炼之三 MyBatis XML方式的基本用法

下一篇: 【MyBatis】 MyBatis修炼之五 MyBatis XML方式的基本用法(INSERT)

推荐阅读更多精彩内容