Mybatis映射文档介绍

mapper映射文档是Mybatis的核心所在,对数据库的操作都在里头了。

在mapper映射文档中,以<mapper/>作为根节点,其下可以有的子节点分别是:

1
select, insert, update, delete, cache, cache-ref, resultMap, parameterMap, sql

本节就来介绍这些子节点。

先来看看 insert

insert, update, delete三个标签的属性和使用方法相似,这里就以insert为例介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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" >
<!-- namespace 的值对应一个DAO -->
<mapper namespace="com.wzt.batis.dao.UserMapper">
            <!-- 1. id 对应DAO中的一个方法 -->
    <insert id="insert"
            <!-- 2. parameterType 将要传入语句的参数的完全限定类名或别名
                这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数 -->
            parameterType="user"
            <!-- 3. parameterMap 可以通过额外的标签来对入参进行更复杂的操作 -->
            parameterMap="uuu"
            <!-- 4. useGeneratedKeys 这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键 
                 5. keyProperty 将获得的主键赋值给指定的Java类中的属性,如此处的id 
                 6. keyColumn 从数据库表的id列取主键值,数据库设定了主键则不需要显式配置 -->
            useGeneratedKeys="true" keyProperty="id" keyColumn="id"
            <!-- 7. flushCache 任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认为true-->
            flushCache="true" 
            <!-- 8. statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。-->
            statementType="PREPARED" 
            <!-- 9. timeout 这个设置是在抛出异常之前,驱动进程等待数据库返回请求结果的秒数。-->
            timeout="20"/>
    
    <parameterMap id="uuu" type="user"/>
</mapper>

代码见真章:

数据库表

1
2
3
4
5
6
7
CREATE TABLE IF NOT EXISTS `tb_user` (
  `id` INT COLLATE utf8_bin NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` VARCHAR (36) COLLATE utf8_bin NOT NULL COMMENT '姓名',
  `age` INT NOT NULL COMMENT '年龄',
  `gender` CHAR (10) COLLATE utf8_bin NOT NULL COMMENT '性别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

DO 类

省略getter/setter方法:

1
2
3
4
5
6
public class UserDo {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
}

Mapper 接口

1
2
3
4
@Mapper
public interface UserMapper {
    Integer insert(UserDo user);
}

Mapper 映射文档

1
2
3
4
5
6
7
8
9
<?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="com.wzt.batis.dao.UserMapper">
    <insert id="insert" parameterType="user">
        INSERT INTO tb_user (id,name,age,gender)VALUES (
          #{id},#{name},#{age},#{gender}
        )
    </insert>
</mapper>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestMain {
    public static void main(String[] args) {
        SqlSession sqlSession = getSessionFactory().openSession(true);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        UserDo user = new UserDo(1,"LiLei",12,"male");
        Integer res = userMapper.insert(user);
    }

    /**
     * Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与数据库进行交互
     */
    private static SqlSessionFactory getSessionFactory() {
        SqlSessionFactory sessionFactory = null;
        String resource = "mybatis-config.xml";
        try {
            sessionFactory = new SqlSessionFactoryBuilder()
                    .build(Resources.getResourceAsReader(resource));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
}

执行后数据库的 tb_user 表中多了一条数据。

userMapper.insert(user)成功执行返回1(失败则为0)。

如果插入时不指定id,采用数据库的自增主键,并想要得到记录的id值,可以这样修改:

1
2
3
4
5
<insert id="insert" parameterType="user" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO tb_user (name,age,gender)VALUES (
      #{name},#{age},#{gender}
    )
</insert>

或者使用 <selectKey/> 标签:

1
2
3
4
5
6
7
8
<insert id="insert1" parameterType="UseR">
    <selectKey keyProperty="id" resultType="int" order="AFTER" >
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO tb_user (name,age,gender)VALUES (
      #{name},#{age},#{gender}
    )
</insert>

如此,在userMapper.insert(user)后便可从user对象中取得id了。

注意: 数据库的自增id赋值给了keyProperty指定的属性,insert方法的返回值仍然是1或0

这里用到了 <selectKey/> 标签,它可以返回记录在数据库中的id,看上去和<insert/>标签的属性useGeneratedKeyskeyProperty功能重复了。 那么除了这个作用外,<selectKey/>还有什么作用呢?

我们知道,MySQL支持主键自增,但是其它的数据库(如oracle)不支持啊;此外如果主键是UUID的,自增也无能为力了。此时<selectKey/>就能派上用场了。

先将DO类的id改成String类型,然后mapper映射文档变成这样:

1
2
3
4
5
6
7
8
9
10
<insert id="insert" parameterType="user">
    <!-- order变成了BEFORE,代表在insert前执行 -->
    <selectKey keyProperty="id" resultType="string" order="BEFORE" >
        SELECT UUID()
    </selectKey>
    <!-- 这里也加上了id列 -->
    INSERT INTO tb_user (id, name, age, gender)VALUES (
      #{id}, #{name}, #{age}, #{gender}
    )
</insert>

这样就会在insert语句前先执行selectKey,将UUID赋给id,然后再插入数据库。

不过在网上看到这里有个隐患,就是通过mybatis生成的UUID可能存在重复,emmm,所以实际工程中还是用java的UUID,更加方便,还不重复!

重头戏 select

先来看看mapper映射文档中,select 标签可以有哪些属性:

1
2
3
4
5
6
7
8
<!-- 大多属性和insert相似,下面列几个多出来的 -->
<select id="select"
        <!-- 下面两个二选一,用来指定返回的类型 -->
        resultType="user" resultMap="uuu"
        <!-- 尝试影响驱动进程每次批量返回的结果行数和这个设置值相等 -->
        fetchSize="10"
        <!-- 设置本条语句的结果是否被二级缓存,默认为true -->
        useCache="true" />

要更全的可以查看相应的官方文档。

现在来看看select的demo。以上面的tb_user表为例,查询某个id的user记录的select为:

1
2
3
<select id="findUserById" parameterType="string" resultType="user">
    SELECT * FROM tb_user WHERE `id` = #{userId}
</select>

DAO层接口为:

1
2
3
4
@Mapper
public interface UserMapper {
    UserDo findUserById(String userId);
}   

可以看到select语句中的#{userId}来自接口方法的入参,返回类型resultType为user(这是个别名)。

这里有个注意点: 入参只有一个时,可以直接这么取;若是有多个,则需要加注解 @Param()了,比如要根据性别和年龄来查:

1
2
3
4
5
@Mapper
public interface UserMapper {
    UserDo findUserByGenderAndAge(@Param("gender") String gender, 
                                  @Param("age") String age);
}   
1
2
3
4
<select id="findUserByGenderAndAge" parameterType="string" resultType="user">
    SELECT * FROM tb_user 
    WHERE `gender` = #{gender} AND `age` = #{age}
</select>

在上面介绍select的属性时提到,返回类型可以用两个属性来指定,resultTyperesultMap,上面的demo用到了resultType,下面讲讲resultMap的用法。

什么都能转 resultMap

resultMap 最简单的用法就是字段的映射。 若数据库中字段和用来接收的Javabean的属性一致时,用resultType即可,但若是不一致,resultMap就派上用场了。 比如说数据库中的字段是uuid,Javabean中是id,此时需要这样配置:

1
2
3
<resultMap id="userMap" type="user">
    <result property="id" column="uuid"/>
</resultMap>

这样就完成了一个简单地字段映射。不过resultMap的强大之处远不止此。

在上面的demo中,返回接收的user的属性都是简单的基本类型的包装类型和String,但如果是个自定义的类,resultMap也可以处理得很好:

现在我们给User类中增加一个Role属性

1
2
3
4
public class UserDo {
    // ...
    private RoleDo role;
}

在数据库中增加两张表,分别是角色表tb_role,和用户角色关联表tb_user_role_relation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 角色表
CREATE TABLE
IF NOT EXISTS tb_role (
	`id` INT NOT NULL AUTO_INCREMENT COMMENT '编号',
	`name` VARCHAR (20) NOT NULL COMMENT '角色名',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COLLATE = utf8_bin;
-- 用户角色关系表
CREATE TABLE
IF NOT EXISTS tb_user_role_relation (
	`id` INT NOT NULL AUTO_INCREMENT COMMENT '编号',
	`user_id` VARCHAR (20) NOT NULL COMMENT '用户id',
	`role_id` VARCHAR (20) NOT NULL COMMENT '角色id',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COLLATE = utf8_bin;

在mapper映射文档中,可以用 <association/> 进行关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="select" resultMap="userMap" >
     SELECT u.*, r.`id` AS role_id, r.`name` AS role_name FROM tb_user u
     LEFT JOIN tb_user_role_relation urr ON u.id = urr.user_id
     LEFT JOIN tb_role r ON urr.role_id = r.id
</select>
<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <result property="age" column="age"/>
    <result property="gender" column="gender"/>
    <result property="name" column="name"/>
    <association property="role" javaType="role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
    </association>
</resultMap>

便可将role和user关联起来:

1
2
3
4
5
6
7
8
9
10
{
    "id": "49",
    "name": "ZhangSan",
    "age": 25,
    "gender": "male",
    "role": {
        "id": 2,
        "name": "manager"
    }
}

在上面这个例子中,一个user只有一个role,但实际上一个user可能对应着多个role,在Javabean中的表现就是:

1
2
3
4
public class UserDo {
    // ...
    private List<RoleDo> roles;
}

此时可以用 <collection/> 进行关联:

1
2
3
4
5
6
7
8
9
10
<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <result property="age" column="age"/>
    <result property="gender" column="gender"/>
    <result property="name" column="name"/>
    <collection property="roles" ofType="role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
    </collection>
</resultMap>

可以得到结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "id": "49",
    "name": "ZhangSan",
    "age": 25,
    "gender": "male",
    "roles": [
        {
            "id": 1,
            "name": "admin"
        },
        {
            "id": 2,
            "name": "manager"
        }
    ]
}

到此,mapper映射文档介绍告一段落。