前言

MyBatis的强大之处就在于它的映射器文件,而这也正是MyBatis的魔力所在,对于任何MyBatis的使用者来说,MyBatis的映射文件是必须要掌握的。

Mapper文件标签

Mapper中一个提供了9个顶层标签,除了1个已经过期的我们不需要去了解,另外8个都是必须要掌握的,只要熟练掌握了标签的使用,使用MyBatis才能如鱼得水。接下来我们就一个个来分析一下这些标签的使用。

  • <cache> – 该命名空间的缓存配置。
  • <cache-ref> – 引用其它命名空间的缓存配置。
  • <resultMap> – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • <parameterMap> – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • <sql> – 可被其它语句引用的可重用语句块。
  • <insert> – 映射插入语句。
  • <update> – 映射更新语句。
  • <delete> – 映射删除语句。
  • <select> – 映射查询语句。

select

select用来映射查询语句,是我们使用最多的一种标签,也是最复杂的一种标签。比如下面就是一个简单的select标签的使用

<select id="listUserByUserName" parameterType="String" resultType="lwUser">
    select user_id,user_name from lw_user where user_name=#{userName}
</select>

select标签内,提供了一些二级标签,下面就列举出了全部的二级标签:

    <select
            id="selectPerson"
            parameterType="int"
            parameterMap="deprecated"
            resultType="hashmap"
            resultMap="personResultMap"
            flushCache="false"
            useCache="true"
            timeout="10000"
            fetchSize="256"
            statementType="PREPARED"
            resultSetType="FORWARD_ONLY"
            databaseId="mysql"
            resultOrdered="false"
            resultSets="xxx,xxx"
            lang="">
        
    </select>

id

必选标签。同一个命名空间里面的唯一标识符,如果需要被外部接口调用,则需要和Mapper接口中的方法名保持一致。

parameterType

可选标签。参数类的完全限定名或别名,上面示例中的表示我们传入的参数是一个String类型(关于别名如果不清楚的可以点击这里)。如果不写这个属性的话,MyBatis在解析xml文件的时候会默认设为unset,然后根据TypeHandler推断出参数类型。如果有多个参数的情况下建议还是不写这个参数,否则可能会出现参数类型转换错误

parameterMap

这是一个过期的属性,我们不做讨论。

resultType

非必选标签。注意:这里的非选是因为resultType和resultMap不能并存,两者能且只能选择一个。主要是用来定义一个返回结果集对象的全限定名或者别名。如果接收参数是一个集合,那么这里定义的就是集合中可以包含的类型,而并不是集合本身。
比如示例中的写法,那么对应Mapper接口中,适用于以下两种语句:

LwUser listUserByUserName(@Param("userName") String userName);
List<LwUser> listUserByUserName(@Param("userName") String userName);

resultMap

非必选标签。注意这里的非选是因为resultType和resultMap不能并存,两者能且只能选择一个。resultMap类型的结果集映射,是MyBatis最强大的特性,在这里我们不展开,过两天会有一篇单独介绍MyBatis一对一和一对多等复杂查询时候会单独介绍该属性。后续介绍。

flushCache

可选标签。设置为 true时,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。

useCache

可选标签。设置为 true时将会导致本条语句的结果被二级缓存,对 select 标签语句默认值为true,对insert,delete,update等语句默认是false。

timeout

可选标签。这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。

fetchSize

可选标签。这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。注意这个只是尝试,假如把fetchSize设置为10万,而数据库驱动最高只支持到5w,那么也会只能返回5w数据

statementType

可选标签。可以选择:STATEMENT,PREPARED 或 CALLABLE 中的一个,这会让 MyBatis 分别使用Statement,PreparedStatement 或 CallableStatement,默认值是PREPARED,也就是使用预编译PreparedStatement 语句。

resultSetType

可选标签。可以选择以下三种类型中的一个,默认为unset(依赖驱动)。

  • FORWARD_ONLY:只允许游标向前访问
  • SCROLL_SENSITIVE:允许游标双向滚动,但不会及时更新数据,也就是说如果数据库中的数据被修改过,并不会在resultSet中体现出来
  • SCROLL_INSENSITIVE:许游标双向滚动,如果数据库中的数据被修改过,会及时更新到resultSet

上面的解释可能有些人还是看不明白,我们先来看一段JDBC读取结果集的操作:

 

 

 

而MyBatis只是把这些操作封装了,底层实际上还是这个操作,rs.next()游标向前滚,其实还有一个rs.previous()表示游标可以向后滚。
所以FORWARD_ONLY只允许向前滚,访问过的数据就会释放内存,在某些场景中可以提升性能。
后面那两个都是允许双向滚动,所以即使访问过得数据,内存也不能释放。这两个的区别就是一个对数据敏感,一个对数据不敏感。

  • 对数据不敏感 就是说当我们查询出结果之后,会将整个结果集都缓存在内存中,假如有一条数据还没读取到(还在while循环中)这时候有另外一个线程修改了这条数据,那么当我们后面读取这条数据的时候,还是读取到修改之前的。
  • 对数据敏感 就是说当我们查询出结果之后,只会缓存一个rowid,而并不会缓存整条数据,假如有一条数据还没读取到(还在while循环中)这时候有另外一个线程修改了这条数据,那么当我们后面读取这条数据的时候,会根据rowid去查询数据,查询到的就是最新的数据。不过需要注意的是,因为delete的时候数据其实还在,只是打了个标记,所以如果一条数据被删除了,是体现不出来的。同理,insert也不影响,因为查询出来的数据不包含insert数据的rowid。

如果对于MySQL的InnoDB引擎的MVCC机制,那么数据肯定是不会敏感的,因为其他事务改了当前事务也看不到。

databaseId

可选标签。数据库厂商id(如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略),详细了解,可以点击这里

resultOrdered

可选标签。这个设置仅针对嵌套结果 select 语句适用。如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值: false 。

resultSets

可选标签。这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。

lang

自定义语言,这个我也没用过,所以就不介绍了

insert

insert用来映射插入语句。以下就是一个insert标签的全部二级标签:

<insert
        id="insertLwUser"
        parameterType="lwUser"
        parameterMap="deprecated"
        flushCache="true"
        statementType="PREPARED"
        keyProperty=""
        keyColumn=""
        useGeneratedKeys=""
        timeout="20"
        databaseId="mysql"
        lang="">

</insert>

有一些标签和select语句是重复的就不在重复介绍,主要来关注一下其他标签。

useGeneratedKeys

可选标签。配置为true时,MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值为false。

keyProperty

可选标签。唯一标记一个属性,MyBatis会将通过getGeneratedKeys 的返回值或者通过insert 语句的selectKey 子元素设置它的键值,默认值是unset 。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表

注意:高版本的mybatis支持批量插入数据的时候返回多条数据的主键ID,低版本的只支持插入一条数据的时候返回该条数据的主键ID

keyColumn

通过生成的键值设置表中的列名,这个设置仅在某些数据库(像PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表

获取自增主键

获取自增主键,可以通过keyProperty来映射
定义一个实体类:

package org.tuniu.mybatis.entity;

public class UserAddress {
    private int id;
    private String address;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "UserAddress{" +
                "id=" + id +
                ", address='" + address + '\'' +
                '}';
    }
}

定义一个UserAddressMapper.java接口:

package org.tuniu.mybatis.mapper;

import org.tuniu.mybatis.entity.UserAddress;

public interface UserAddressMapper {
    int insertUserAddress(UserAddress userAddress);
}

注意:这里参数如果直接只有一个的话可以不适用@Param注解,这样在xml文件中可以直接使用JavaBean内的属性名。如果使用了@Param注解,如下:

int insert(@Param("userAddress") UserAddress userAddress);

那么xml文件中就可以使用#{userAddress.属性名}来获取属性JavaBean内的属性

定义一个UserAddressMapper.xml映射文件(keyProperty="id"表示把主键的值设置到参数UserAddress类中的属性id):

 

<?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接口 -->
<mapper namespace="org.tuniu.mybatis.mapper.UserAddressMapper">

    <insert id="insertUserAddress" parameterType="org.tuniu.mybatis.entity.UserAddress" useGeneratedKeys="true"
            keyProperty="id">
       insert into lw_user_address (address) values (#{address})
   </insert>
</mapper>

mybatis-config.xml中要新增mapper映射文件配置:

<!--要加载的xml文件-->
<mappers>
    <mapper resource="org/tuniu/mybatis/mapping/UserAddressMapper.xml"></mapper>
</mappers>

也可以这么写(一劳永逸):

<mappers>
    <package name="org.tuniu.mybatis.mapper"/>
</mappers>

将mapp接口和mapper对应的xml进行放入同一个文件夹:

 

 

 然后写一个测试类:

package org.tuniu.mybatis;

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.tuniu.mybatis.entity.UserAddress;
import org.tuniu.mybatis.mapper.UserAddressMapper;

import java.io.IOException;
import java.io.InputStream;

public class TestMyBatisInsert {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        //读取mybatis-config配置文件
        InputStream stream = Resources.getResourceAsStream(resource);
        //创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream, "dev");
        //创建SqlSession对象
        SqlSession session = sqlSessionFactory.openSession();
        try {
            UserAddress userAddress = new UserAddress();
            userAddress.setAddress("广东深圳");
            UserAddressMapper mapper = session.getMapper(UserAddressMapper.class);
            int count = mapper.insertUserAddress(userAddress);
            session.commit();
            System.out.println("插入成功数:" + count);
            System.out.println("插入数据的主键为:" + userAddress.getId());
        } finally {
            if (null != session) {
                session.close();
            }
        }
    }
}

输出结果(成功获取到了插入数据的主键):

 

获取自增主键(多条)

 UserAddressMapper.xml文件加入:

<insert id="insertUserAddressList" parameterType="list" useGeneratedKeys="true"
        keyProperty="id">
    insert into lw_user_address  (address) values
    <foreach collection="list" item="item"  separator=",">
        (#{item.address})
    </foreach>
</insert>

UserAddressMapper.class文件加入:

int insertUserAddressList(List<UserAddress> list);

测试类:

package org.tuniu.mybatis;

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.tuniu.mybatis.entity.UserAddress;
import org.tuniu.mybatis.mapper.UserAddressMapper;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class TestMyBatisInsert {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        //读取mybatis-config配置文件
        InputStream stream = Resources.getResourceAsStream(resource);
        //创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream, "dev");
        //创建SqlSession对象
        SqlSession session = sqlSessionFactory.openSession();
        try {
            UserAddress userAddress = new UserAddress();
            UserAddress userAddress1 = new UserAddress();
            UserAddress userAddress2 = new UserAddress();
            userAddress.setAddress("广东深圳");
            userAddress1.setAddress("广东珠海");
            userAddress2.setAddress("广东汕头");
            UserAddressMapper mapper = session.getMapper(UserAddressMapper.class);
//            int count = mapper.insertUserAddress(userAddress);
            List<UserAddress> list = new ArrayList<UserAddress>();
            list.add(userAddress);
            list.add(userAddress1);
            list.add(userAddress2);
            int count = mapper.insertUserAddressList(list);
            session.commit();
            System.out.println("插入成功数:" + count);
            System.out.print("插入数据的主键为:");
            for (UserAddress address : list) {
                System.out.print(address.getId()+",");
            }
        } finally {
            if (null != session) {
                session.close();
            }
        }
    }
}

返回结果:

 

 

 

通过selectKey获取自定义列

 假如有些数据库不支持自增主键,或者说我们想插入自定义的主键,而又不想在业务代码中编写逻辑,那么就可以通过MyBatis的selectKey来获取。(不建议实际使用,这里只是为了展示 MyBatis 处理问题的灵活性和宽容度)
UserMapper.java中新建一个方法:


int insert2(LwUser lwUser);

然后在UserMapper.xml中对应新增一个insert2语句:

<insert id="insert2" useGeneratedKeys="true" keyProperty="userid">
    <selectKey keyProperty="userId" resultType="string" order="BEFORE">
        select RAND()  from lw_user limit 1
    </selectKey>
    insert into lw_user(user_id,user_name) values (#{userId},#{userName})
</insert>

然后修改测试类:

try {
    LwUser lwUser = new LwUser();
    lwUser.setUserName("xiaoli");
    UserMapper userAddress = session.getMapper(UserMapper.class);
    int i = userAddress.insert2(lwUser);
    session.commit();
    System.out.println("插入成功数:" + i);
    System.out.println("插入数据的username为:" + lwUser.getUserId());
}finally {
    session.close();
}

输出结果如下,成功获得了我们自定义的username:

插入成功数:1
插入数据的username为:0.1481683348754126

 

 selectKey中的order属性有2个选择:BEFORE和AFTER。

  • BEFORE:表示先执行selectKey的语句,然后将查询到的值设置到JavaBean对应属性上,然后再执行insert语句。
  • AFTER:表示先执行AFTER语句,然后再执行selectKey语句,并将selectKey得到的值设置到JavaBean中的属性。上面示例中如果改成AFTER,那么插入的address就会是空值,但是返回的JavaBean属性内会有值。

PS:selectKey中返回的值只能有一条数据,如果满足条件的数据有多条会报错,所以一般都是用于生成主键,确保唯一,或者在selectKey后面的语句加上条件,确保唯一

 

update

insert用来映射更新语句。以下就是一个undate标签的全部二级标签:

 

<update
        id="UpdateLwUser"
        parameterType="lwUser"
        parameterMap="deprecated"
        flushCache="true"
        statementType="PREPARED"
        keyProperty=""
        keyColumn=""
        useGeneratedKeys=""
        timeout="20"
        databaseId="mysql"
        lang="">
    
</update>

这个标签和insert基本一致,就不重复解释了。

delete

delete用来映射删除语句。以下就是一个delete标签的全部二级标签:

<delete
        id="insertLwUser"
        parameterType="lwUser"
        parameterMap="deprecated"
        flushCache="true"
        statementType="PREPARED"
        timeout="20"
        databaseId="mysql"
        lang="">
    
</delete>

这里的标签除了少了useGeneratedKeys,keyProperty和keyColumn三个标签之外,其余的和insert,update一样。

sql

这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。
如下是一个最简单的例子:

    <select id="selectUserAddress" resultType="org.tuniu.mybatis.entity.UserAddress">
        select <include refid="myCloumn"></include> from lw_user_address
    </select>

    <sql id="myCloumn" >
        id,address
    </sql>

等同于

    <select id="selectUserAddress" resultType="org.tuniu.mybatis.entity.UserAddress">
        select id,address from lw_user_address
    </select>

如果说我们现在需要定义一个关联语句,列来自于两张不同的表,又该如何实现呢?这时候就可以通过制定参数的方式来实现了:

    <select id="selectUserAddress1" resultType="org.tuniu.mybatis.entity.UserAddress">
        select
        <include refid="myCloumn1">
            <property name="prefix1" value="u"/>
            <property name="prefix2" value="j"/>
        </include>
        from lw_user u inner join  lw_user_job j on u.user_id=j.user_id
    </select>

    <sql id="myCloumn1" >
        ${prefix1}.user_id,${prefix2}.id
    </sql>

这时候打印出来的sql语句如下:

select u.user_id,j.id from lw_user u inner join lw_user_job j on u.user_id=j.user_id

cache

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。但是默认情况下只开启了一级缓存,即局部的session缓存,如果想要开启二级缓存。那么就需要使用到cache标签

    <cache
            type="org.tuniu.xxx"
            eviction="FIFO"
            flushInterval="60000"
            readOnly="true"
            size="512"/>

PS:这些属性都是有默认值的,所以一般情况下可以直接使用:

<cache/>

type

如果说我们自己自定义了缓存,那么这里可以配置自定义缓存类的全限定名或者别名,如果没有自定义缓存,则不需要配置type属性。

关于缓存相关原理以及如何自定义缓存,后面会有一篇文章专门介绍缓存,本文主要还是介绍一下标签的使用,不会过多涉及底层原理性问题。

eviction

缓存回收策略,MyBatis中more提供了以下策略可以选择:

  • LRU:最近最少使用算法(默认算法)。移除最长时间不被使用的对象(技术是相通的发现没有,和redis的回收策略相似吧)
  • FIFO:先进先出算法。按对象进入缓存的顺序来移除它们
  • SOFT:软引用。移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK:弱引用。更积极地移除基于垃圾收集器状态和弱引用规则的对象

flushInterval

刷新间隔时间(单位是毫秒)。可以被设置为任意的正整数。默认情况是不设置,也就是不会主动刷新缓存(只有等待sql被执行的时候才会被刷新)。

readOnly
是否只读。属性可以被设置为 true 或 false。如果设置为true,那么只读的缓存会给所有调用者返回缓存对象的相同示例,因为缓存无法被修改。这在一定程度上可以提升性能。
默认是false,也就是可以修改缓存,那么当读取缓存的时候会通过序列化的方式返回缓存对象的拷贝,虽然这么做会慢一点,但是安全,因此默认才会设置为false,允许修改缓存。

size

引用数目。通俗点就是可以缓存的个数默认值是1024。超过了设置值的时候,就会采用上面的算法进行覆盖

cache-ref

假如我们在其中一个Mapper中已经配置好了缓存,然后在其他Mapper想要共用,那么在这样的情况下就可以使用cache-ref元素来引用另外一个缓存,从而不需要重复配置。如:

 

<cache-ref namespace="org.tuniu.mybatis.mapper.UserAddressMapper"/>

这样当前Mapper就可以共用UserMapper文件中的相同缓存了。

需要注意的是,被引用的namespace自己本身要开启了二级缓存,否则会报错:

 

 

resultMap

上面介绍select标签的时候提到,select标签的返回结果可以使用resultMap,但是一旦我们使用了resultMap时,我们就必须要自己定义一个resultMap。
如下,我们定义了一个resultMap:

<resultMap id="JobResultMap" type="lwUser">
    <result column="user_id" property="userId" jdbcType="VARCHAR" />
    <result column="user_name" property="userName" jdbcType="VARCHAR" />
</resultMap>

这时候select语句就可以引用:

<select id="selectUserAndJob" resultMap="JobResultMap">
    select * from lw_user
</select>

可以看到,resultMap可以自由定义,所以可以接受非常复杂的查询返回结果集,resultMap的具体使用,可以关注下一篇

parameterMap

这个参数已过期,不再讨论,可以忘掉有这个参数

 

总结


本文主要介绍了MyBatis中映射文件Mapper.xml文件一些标签的使用,可以算是最全MyBatis中XML映射文件标签分析了,当然这其中并不涉及到如何完成动态sql语句的拼写。动态sql也是MyBatis的一个非常重要的功能点,但是综合考虑如果单纯通过一篇文章来书写sql语句动态标签的使用,会显得非常枯燥,所以动态sql的映射就不准备通过单独的文章来书写了,虽然如此,但是依然会通过一些相关知识点来介绍动态标签的使用,比如下一篇介绍MyBatis中一对多和多对多的结果集如何返回时,就会涉及到一些标签,如where,if,set,choose等,后面还会介绍批量操作等,也会涉及到foreach等一些标签的使用。

 

参考链接:mybatis官网


————————————————
版权声明:本文为CSDN博主「双子孤狼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zwx900102/article/details/108534736

posted on 2021-04-18 10:43  幂次方  阅读(1868)  评论(0编辑  收藏  举报