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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容