美文网首页我爱编程MyBatis
MyBatis XML使用方式

MyBatis XML使用方式

作者: lihongyan | 来源:发表于2018-05-15 17:30 被阅读0次

    内容:

    • select用法
    • insert用法
    • update用法
    • delete用法
    • 多个接口参数的用法
    • 动态代理实现原理

    select用法

    MyBatis的select用法,重点是resultMap、resultType的使用

    先上一段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 user_name,
                user_email,
                user_info
            from sys_user 
            where id = #{id}
        </select>
    </mapper>
    

    需要注意的内容:

    • MyBatis是使用XML中的select标签的id属性值和定义的接口方法名相一致来将接口方法和XML中定义的SQL语句关联到一起的,如果接口方法没有和XML中的id属性值相对应,启动程序便会报错。
    • 当只是用XML而不使用接口的时候,namespace的值可以设置为任意不重复的名称
    • 标签的id属性值在任何时候都不能出现英文句号“.”,并且同一命名空间下不能出现重复的id
    • 因为接口方法是可以重载的,所以接口中可以出现多个同名但参数不同的方法,但是XML中id的值不能重复,因而接口中所有同名方法会对应着XML中的同一个id的方法

    resultMap标签用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到对象的属性上。

    resultMap标签包含的所有属性有:

    • id:必填项,唯一,用于在namespace中标示resultMap
    • type:必填项,用于配置查询列所映射到的Java对象类型
    • extends:可选项,可以配置当前的resultMap继承自其它的resultMap,属性值为继承resultMap的id
    • autoMapping:可选项,可选值为true或false,用于配置是否启用非映射字段(没有在resultMap中配置的字段)的自动映射功能,该配置可以覆盖全局的autoMappingBehavior配置

    resultMap包含的标签有:

    • id:标记结果作为唯一值,可以帮助提高整体性能
    • result:注入到Java对象属性的普通结果
    • constructor:配置使用构造方法注入结果

    id标签和result标签包含的属性值:

    • column:从数据库中得到的列名,会这是列的别名
    • property:映射到列结果的属性,可以简单映射,也可以借助“.”进行嵌套映射
    • javaType:Java类的完全限定名
    • jdbcType:列对应的数据库类型
    • typeHeader:使用这个属性可以覆盖默认的烈性处理器,属性值为类的完全限定名或类型别名

    需要注意的内容:

    • 可以通过在resultMap中配置property和column属性的映射,或者在SQL中设置别名这两种方式实现将查询列映射到对象属性上。

    • property属性或别名要和对象中属性的名字相同,但实际匹配时,MyBatis会先将两者转换为大写大写形式,然后再判断是否相同。假设对象有一个userName属性,那么property="username"或property="userName"都可以匹配到对象的userName属性。
      因此,在设置property属性或别名的时候,不需要考虑大小写是否一致,但为了便于阅读,尽量按统一的规则来设置。

    • MyBatis自带了将下划线的命名转化为驼峰式的命名的功能,要想启用这个功能,只需要在MyBatis的配置文件中增加如下配置:
    <setting name="mapUnderscoreToCamelCase" value="true" />
    

    constructor标签包含的子标签有:

    • idArg:标记结果作为唯一值,可以帮助提高整体性能
    • arg:注入到构造方法的一个普通结果

    constructor中的idArg、arg分别对应着resultMap中的id、result标签,它们的含义相同,只是注入方式不同

    考虑一种情况:在使用resultType的前提下,假设有用户表、角色表、权限表

    第一种简单的情形:根据用户id获取用户拥有的所有角色,返回的结果为角色集合,结果只有角色的信息,不包含额外的其他字段信息,这时直接使用resultType="SysRole"即可。

    第二种情形:根据用户id获取用户拥有的所有角色,并且需要查询出用户名和用户邮件。

    • 第一种解决方法就是在SysRole对象中直接添加userName属性和userEmail属性,这样仍然使用SysRole作为返回值即可。

    • 第二种方法:创建一个如下所示的对象:

    public class SysRoleExtend extends SysRole {
        private String userName;
        private String userEmail;
    }
    

    这样就可以将resultType设置为SysRoleExtend即可。

    • 第三种方法:直接在SysRole中添加SysUser对象,字段名为user。增加了这个字段后,修改对应的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>
    

    user是SysRole中刚刚增加的属性,userName和userEmail是SysUser对象中的属性,通过这种方式可以直接将值赋给user字段中的属性


    insert用法

    insert的用法比较简单,只有让它返回生成的主键值时,由于不同数据库的主键生成方式不同,这种情况会有一些复杂。

    insert标签包含的属性有:

    • id:命名空间中的唯一标识符。

    • parameterType:传入的参数的完全限定类名或别名。这个属性是可选的,因为MyBatis可以推断出传入语句的具体参数,因此不建议配置该属性。

    • flushCache:默认值为true,任何时候只要语句被调用,都会清空一级缓存和二级缓存。

    • timeout:驱动程序等待数据库返回请求结果的秒数。

    • statementType:可选值有STATEMENT、PREPARED、CALLABLE,MyBatis会分别使用对应的Statement、PrepareStatement、CallableStatement,默认值为PREPARED。

    • useGeneratedKeys:默认值为true。如果设置为true,MyBatis会使用JDBC的getGeneratedKeys方法来去除由数据库内部生成的主键。

    • keyProperty:MyBatis通过getGeneratedKeys获取主键值后将要赋值的对象的属性名。如果希望得到多个数据库自动生成的列,属性值也可以是以逗号分隔的属性名称列表

    • databaseId:多数据库支持。前提是在MyBatis-config.xml中配置了databaseIdProvider。如果配置了,那么MyBatis会加载所有不带databaseId或匹配当前databaseId的语句。如果同时存在带databaseId和不带databaseId的语句,后者会被忽略。

    简单的insert

    上一段简单的XML代码:

    <insert id="insert">
        insert into sys_user(
            user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
    </insert>
    

    此处的<insert>中的SQL就是一个简单的INSERT语句,将所有的列都列举出来,在values中通过#{property}的方式参数中取出属性的值。为了防止类型错误,对于一些特殊的数据类型,建议制定具体的jdbcType值。例如,headImg制定BLOB类型,createTime制定TIMESTAMP类型。

    • BLOB对应的Java类型是ByteArrayInputStream
    • 由于数据库区分date、time、datetime类型,但是Java中一般使用java.util.Date类型。因此为了保证数据类型的正确,需要手动制定日期类型。date、time、datetime对应的JDBC类型分别为Date、Time、Timestamp
    • 数据库的datetime类型可以存储DATE(时间部分默认为00:00:00)和TIMESTAMP这两种类型的时间,不能存储TIME类型的时间;date类型可以存储DATE和TIMESTAMP类型的时间;只有time类型才能存储TIME类型的时间

    返回主键

    上述语句默认返回影响的行数,而不是生成的主键。如果想要返回生成的主键,可以使用如下的两种方法

    • 使用JDBC方式返回主键自增的值
    • 使用selectKey返回主键的值

    使用JDBC方式返回主键自增的值

    这种方式适用于能够提供主键自增的数据库中,例如MySQL、SQL Server

    直接上代码:

    <insert id="insert2" useGeneratedKeys="true" keyProperty="id">
        insert into sys_user(
            user_name, user_password, 
            <if test="userEmail != null">
                <if test="userEmail != ''">
                user_email, 
                </if>
            </if>
            user_info, head_img, create_time)
        values(
            #{userName}, #{userPassword}, 
            <if test="userEmail != null">
                <if test="userEmail != ''">
                #{userEmail}, 
                </if>
            </if>
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
    </insert>
    

    通过useGeneratedKeys和keyProperty可以实现自增的需求。useGeneratedKeys设置为true,MyBatis使用JDBC的getGeneratedKeys方法获取数据库生成的主键,并将结果赋值给keyProperty指定的对象属性值。当需要获取多个数据库自动生成的值时,使用逗号隔开keyProperty的属性值,并设置keyColumn属性,按顺序指定数据库的列,这里要求keyColumn中列的顺序与keyProperty中属性的顺序一一对应。

    使用selectKey返回主键的值

    对于不提供主键自增功能的数据库,我们需要先使用序列得到一个值,然后将这个值赋给id,再将数据插入数据库,MyBatis提供了<selectKey>标签来完成该需求。这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。

    mysql
    <insert id="insert3">
        insert into sys_user(
            user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
        <selectKey keyColumn="id" keyProperty="id" resultType="long" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
    </insert>
    
    oracle
    <insert id="insertOracle">
        <selectKey keyColumn="id" keyProperty="id" resultType="long" order="BEFORE">
            SELECT SEQ_USER.nextval from dual
        </selectKey>
        insert into sys_user(
            id, user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{id}, #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
    </insert>
    
    • <selectKey>标签的keyColumn、keyProperty和上面useGeneratedKeys的用法含义相同
    • resultType用于设置返回值类型;
    • order属性的设置和使用的数据库有关:mysql中,order属性设置为AFTER,因为当前记录的主键值在insert语句执行成功后才能回去到;Oracle中设置为BEFORE,因为Oracle需要先从序列获取值,然后将值作为主键插入到数据库中。
    • <selectKey>标签放置的位置不会影响处理的结果,真正有影响的是order属性

    下面列举一些在<selectKey>标签中常用的SQL

    • Oracle :select SEQ_ID.nextval from dual
    • MySQL :select LAST_INSERT_ID()
    • DB2 :values IDENTITY_VAL_LOCAL()
    • SQL Server :select SCOPE_IDENTITY()

    update用法

    基本的update用法,比较复杂的update用法是结合<if>标签来更新不为null的值,以及使用<foreach>实现的批量更新,以上两种用法需要使用MyBatis的动态SQL。

    <update id="updateById">
        update sys_user 
        set user_name = #{userName},
            user_password = #{userPassword},
            user_email = #{userEmail},
            user_info = #{userInfo},
            head_img = #{headImg, jdbcType=BLOB},
            create_time = #{createTime, jdbcType=TIMESTAMP}
        where id = #{id}
    </update>
    

    delete用法

    <delete id="deleteById">
        delete from sys_user where id = #{id}
    </delete>
    

    对应的Mapper.java中的方法可以为:

    deleteById(Long id);
    
    // 或
    
    deleteById(SysUser user);
    

    多个接口参数

    在实际应用中经常会遇到使用多个参数的情况。一种方法是,将多个参数合并到一个JavaBean中,并使用这个JavaBean作为接口方法的参数。这种方法使用起来很方便,但不适合全部的情况,因为不能只为了两三个参数去创建新的JavaBean。因此,对于参数比较少的情况,还有两种方式可以采用:使用Map类型作为参数;使用@Param注解。

    使用Map类型作为参数的方法,就是在Map中通过key来映射XML中SQL使用的参数值名字,value用来存放参数值,需要多个参数时,通过Map的key-value方式传递参数值,由于这种方式还需要自己手动创建Map以及对参数进行赋值,其实并不简洁。所以在实际使用时,当有多个参数时,推荐使用@Param的方式。接下来先介绍不使用@Param注解时,使用多个参数的问题

    <select id="selectByUser" 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
        where 1 = 1
            and user_id = #{userId}
            and enabled = #{enabled}
    </select>
    

    对应的接口方法为:

    SysUser selectByUser(Long userId, String enabled);
    

    调用selectByUser方法时,MyBatis会抛出一下异常:

    Parameter 'userId' not found
    Available parameters are [0, 1, param1, param2]

    这个错误标示,XML可用的参数只有0、1、param1、param2,没有userId。没有0和1,param1和param2都是MyBatis根据参数位置自定义的名字。这时如果将XML中的#{userId}改为#{0}或#{param1},将#{enabled}改为#{1}或#{param2},selectByUser方法就会被正常调用了,在实际使用时并不建议这么做。

    接下来介绍使用@Param的情况

    修改接口方法为:

    SysUser selectByUser(@Param("userId")Long userId, @Param("enabled") String enabled);
    

    这时的XML文件中对应的SQL的可用参数编程了[userId, enabled, param1, param2],如果把#{userId}变成#{param1},#{enabled}变成#{param2}方法也能正常执行。

    实际上,给参数配置@Param注解后,MyBatis就会自动将参数封装成Map类型,@Param注解值会作为Map中的key,参数值作为Map中的value。

    当只有一个参数的时候,MyBatis不关系这个参数叫什么名字就会直接把这个唯一的参数值拿来使用。

    以上内容使用的多个参数都是基本类型或包装类型,当参数是对象类型时,情况略有不同,假设接口方法为

    SysUser selectByUser(@Param("user")SysUser user, @Param("role") SysRole role);
    

    则XML中也需要做对应的修改:

    <select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
        select id, 
            u.user_name userName, 
            u.user_password userPassword,
            u.user_email userEmail,
            u.user_info userInfo,
            u.head_img headImg,
            u.create_time createTime
        from sys_user u 
            inner join sys_user_role ur on u.user_id = ur.user_id
            inner join sys_role r on ur.role_id = r.role_id
        where 1 = 1
            and u.user_id = #{user.userId}
            and r.enabled = #{role.enabled}
    </select>
    

    当参数是对象是,就不能直接使用#{userId}和#{enabled}了,而是要通过点取值方式使用#{user.id}和#{role.enabled}从两个JavaBean中取出指定属性的值。

    • [ ] 除了以上常用的参数类型外,接口的参数还可能是集合或数组

    动态代理

    为什么Mapper接口没有实现类却能被正常调用呢?这是因为MyBatis在Mapper接口上使用了动态代理的机制

    假设存在如下Mapper接口:

    public interface UserMapper{
        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>
    

    使用Java动态代理方式创建一个代理类:

    public class MapperProxy<T> implements InvocationHandler {
        private Class<T> mapperInterface;
        
        private SqlSession sqlSession;
        
        public MapperProxy(Class<T> mapperInterface, SqlSession sqlSession){
            this.mapperInterface = mapperInterface;
            this.sqlSession = sqlSession;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Objectp[] args) throws Throwable{
            // 需要代理的接口中的方法
            String methodName = mapperInterface.getCanonicalName() + "." + method.getName();
            
            // 将接口中的方法代理到SqlSession中的对应方法
            List<T> list = sqlSession.selectList(methodName);
            
            // 返回结果
            return list;
        }
    }
    

    测试代码如下:

    // 构建代理类
    MapperProxy userMapperProxy = new MapperProxy(UserMapper.class, sqlSession);
    
    // 获取UserMapper接口的代理类
    UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[]{ UserMapper.class },
            userMapperProxy
        );
        
    // 调用UserMapper接口中的selectAll()方法,实际上被代理到了SqlSession中的方法
    List<SysUser> users = userMapper.selectAll();
    

    相关文章

      网友评论

        本文标题:MyBatis XML使用方式

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