MyBatis XML使用方式

内容:

  • 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();

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