美文网首页MyBatis修炼MyBatis我爱编程
【MyBatis】 MyBatis修炼之四 MyBatis

【MyBatis】 MyBatis修炼之四 MyBatis

作者: 开心跳蚤 | 来源:发表于2017-08-15 15:20 被阅读276次

    我们执行查询操作,使用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)

    相关文章

      网友评论

        本文标题:【MyBatis】 MyBatis修炼之四 MyBatis

        本文链接:https://www.haomeiwen.com/subject/ersurxtx.html