MyBatis专题(二)-映射文件

MyBatis的强大功能当属mapper映射文件,开发者可以在映射文件中配置SQL语句、参数映射和结果集映射信息来简化与数据库交互的编码工作。映射文件帮助开发者略去大部分繁杂的JDBC代码,方便持久层后期的维护扩展工作,让开发者把关注点集中于业务代码。

本文将从大的方面介绍mapper映射文件的编写与使用,映射文件本身有着非常多的知识点,不能在本文中一一细说,有些知识点需要单独介绍。

基础标签

上一篇的“入门”已经把MyBatis映射文件的大概结构介绍出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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="UserMapper">
<insert id="insert">
INSERT INTO user(id, username, password) VALUES(#{id}, #{username}, #{password})
</insert>
<select id="selectById" resultType="java.util.Map">
SELECT * FROM user WHERE id=#{id}
</select>
<update id="updateUsernameById">
UPDATE user SET username=#{username} WHERE id=#{id}
</update>
<delete id="deleteById">
DELETE FROM user WHERE id=#{id}
</delete>
</mapper>

mapper是xml格式文件,有自己的DTD引用,配置内容的主体在<mapper>中,<mapper>标签有一个关键的属性namespace表示当前mapper的命名空间,其作用与Java的package非常类似,它起到了划分模块和定位SQL操作片段的作用。

mapper文件中预定义了几个对应数据库操作的标签:

  • <select> 查询SQL
  • <insert> 插入SQL
  • <update> 更新SQL
  • <delete> 删除SQL

当然,<mapper>中不止有这几个标签。

上面的mapper示例文件中,定义了一个命名空间为UserMapper的mapper,mapper定义了两个操作<insert>select。可以发现,这两个标签都带有一个id属性,id属性起到标识定位的作用,同一个mapper中不能存在相同id的操作。一下的编码中我们使用到id来引用某一段SQL操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.oopsguy.mybatistopic;

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 java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class Main {

public static void main(String[] args) throws IOException {
//获取配置文件信息
InputStream resStream = Resources.getResourceAsStream("mybatis-config.xml");
//通过配置参数构建sqlSessionFactory工厂类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resStream);
//开启数据库会话
SqlSession sqlSession = sqlSessionFactory.openSession();

int id = 1;
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", id);
params.put("username", "hello");
params.put("password", "world");

//使用命名空间为UserMapper的id为insert的SQL执行insert操作
sqlSession.insert("UserMapper.insert", params);
sqlSession.commit();

params.clear();
params.put("username", "oopsguy");
params.put("id", id);
sqlSession.update("UserMapper.updateUsernameById", params);
sqlSession.commit();

//使用命名空间为UserMapper的id为selectById的SQL段执行select操作,取一条结果集
Map<String, Object> result = sqlSession.selectOne("UserMapper.selectById", id);
System.out.println("添加修改后:" + result);

//使用UserMapper的id为deleteById的SQL段执行删除操作
sqlSession.delete("UserMapper.deleteById", id);
sqlSession.commit();

//检查结果是否存在
result = sqlSession.selectOne("UserMapper.selectById", id);
System.out.println("删除后: " + result);

//释放资源
sqlSession.close();
}

}

可以看到在使用SqlSession类提供了insert方法执行添加数据,insert方法参数:

1
insert(String statement, Object param)

第一个参数代表具体的SQL操作片段,您需要传入一个<insert>SQL片段。可以通过命名空间.id格式来引用。上面的insert就是使用命名空间为UserMapper的id为insert的SQL段。SqlSession的selectOne()update()delete()也是一样的方式。

SQL参数

在开发过程中,很少有固定不变的SQL,我们需要为SQL语句进行动态传参,以应付不同的场景。经过之前的介绍,您可能已经发现了mapper文件中的SQL部分地方带有#{}这样的语法,这是MyBatis的SQL的占位符,类似于JDBC中的?占位符。不过MyBatis更加强大。如果在只有一个占位符的情况下,MyBatis把#{}当作普通占位符,解析时把传递进来的参数替换即可。如果是多个参数(SqlSession提供的接口最多只能传递一个参数,为Object类型,可以是原生类型、包装类型、Map或者Java Bean),您需要使用Map或者Java Bean对象传参。如果参数是Map类型,MyBatis会根据占位符名称来匹配Map的key取值,如果是Java Bean,MyBatis会根据占位符名称匹配Bean的属性字段取值。可以为语句添加parameterType属性指定参数的类型。如:

1
2
3
<insert id="insert" parameterType="java.util.Map">
INSERT INTO user(id, username, password) VALUES(#{id}, #{username}, #{password})
</insert>

以上指定了参数的类型为Map(默认不必声明),您需要为parameterType补全完整类名(包路径和类名),虽然后边有方法可以通过配置“别名”来为自定义类型省略全名。

使用Java Bean传参

简单的几个参数,沃恩可以通过Map来包装传递,但是当参数达到十几个以上,使用Map将不再是一个好方法,为了使业务更加清晰,程序便于维护,使用Java Bean来映射参数是一个非常好的习惯,符合面向对象的开发习惯。以下我们将用一个Java Bean来作为一个insert操作的参数。

新建一个Java类User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.oopsguy.mybatistopic.bean;

public class User {

private Integer id;
private String username;
private String password;

public Integer getId() {
return id;
}

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

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

新建一个mapper UserBeanMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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="UserBeanMapper">
<insert id="insert" parameterType="com.oopsguy.mybatistopic.bean.User">
INSERT INTO user(id, username, password) VALUES(#{id}, #{username}, #{password})
</insert>
<select id="selectByUsername" resultType="java.util.Map">
SELECT * FROM user WHERE username=#{username}
</select>
</mapper>

UserBeanMapper.xml中定义了insertselect操作,insert的参数指定了parameterTypeUser类,需要注意的是#{}占位符中的参数名称需要与Bean中的属性名称对应,涉及到的属性,Bean也需要提供getter访问器,MyBatis需要通过反射来获取Bean对象的值。由于id字段设置为自增主键,所以无需手动设值。

之后需要把UserBeanMapper.xml文件配置到mybatis-config.xml的mapper配置中:

1
2
3
4
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/UserBeanMapper.xml"/>
</mappers>

让我们编写代码测试一下(BeanParameterMain.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.oopsguy.mybatistopic;

import com.oopsguy.mybatistopic.bean.User;
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 java.io.InputStream;
import java.util.Map;

public class BeanParameterMain {

public static void main(String[] args) {
SqlSession sqlSession = null;

try {
InputStream resStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resStream);
sqlSession = sqlSessionFactory.openSession();

String username = "JavaBean";
User user = new User();
user.setUsername(username);
user.setPassword("HelloWorld");
sqlSession.insert("UserBeanMapper.insert", user);
sqlSession.commit();

Map<String, Object> ret = sqlSession.selectOne("UserBeanMapper.selectByUsername", username);
System.out.println(ret);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}

}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
==>  Preparing: INSERT INTO user(id, username, password) VALUES(?, ?, ?) 
==> Parameters: null, JavaBean(String), HelloWorld(String)
<== Updates:
...
==> Preparing: SELECT * FROM user WHERE username=?
==> Parameters: JavaBean(String)
<== Columns: id, username, password
<== Row: 3, JavaBean, HelloWorld
<== Total: 1
{password=HelloWorld, id=3, username=JavaBean}
...

可以看出,传参方式并没有什么区别,只是把Map换成了User类。其他的类型依旧如此。

结果集处理

在上面的示例中,我们把select到的结果用Map包装。其实实际开发中一条SQL语句返回的结果很少这么简单,有些结果字段几十甚至上百个,数据量也庞大,使用Map来接收结果集,从中取值是件非常麻烦的事情。MyBatis为我们提供了结果集类型设定和映射处理功能,可以像参数处理方式一样指定类型,当然最强大的还是结果集映射,这个后边再说。我们先说说怎么把把结果集映射到Java Bean对象,相信这也是Java开发者在做数据库层中经常使用的结果集处理方式吧。

感觉有点ORM(Object Relational Mapping,对象关系映射)的味道,是不是?当然MyBatis不是ORM框架,MyBatis的参数映射处理和结果集映射处理都是需要用户去指定,而不是自动完成,像经典的Hibernate框架从实体映射,到对象查询转SQL语句再到结果集映射都是自动完成,而MyBatis更像是“手动ORM”,您需要配置参数映射、书写SQL语句和配置结果集映射,而这些过程大部分都能让ORM框架自动完成。

要指定结果集的映射信息,可以指定resultTyperesuktMap属性的值:

  • resultType:结果集类型
  • resultMap:结果的映射关系

还是跟参数映射的示例差不多,使用User类作为select之后得到结果集的装载对象。

新建一个mapper文件(BeanResultMapper.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="BeanResultMapper">
<insert id="insert" parameterType="com.oopsguy.mybatistopic.bean.User">
INSERT INTO user(id, username, password) VALUES(#{id}, #{username}, #{password})
</insert>
<select id="selectByUsername" resultType="com.oopsguy.mybatistopic.bean.User">
SELECT * FROM user WHERE username=#{username} LIMIT 1
</select>
<select id="selectAll" resultType="com.oopsguy.mybatistopic.bean.User">
SELECT * FROM user
</select>
</mapper>

以上mapper有三个操作,insert与之前一样,还有两个select操作。第一个id为selectByUsername是获取一个结果,指定resultType返回结果是User类型,第二个是id为selectAll的语句,获取user表中所有结果,指定resultType结果集类型为User类型,由于是多个结果,MyBatis会返回List

需要注意的是,返回的结果字段中需要与指定的Java Bean中的属性名一致,如果不一致,可以在SQL中使用字段别名来修正,还有一种方式是使用resultMap指明具体的映射关系,这个后面再讲。

字段修正:

1
2
3
4
5
6
7
8
<select id="selectOne" resultType="com.oopsguy.mybatistopic.bean.User">
SELECT
uid AS id,
uname AS username,
pwd AS password
FROM
user
</select>

记得把新添的BeanResultMapper.xml文件添加到mybatis-config.xml配置文件中的<mappers>中,之后,编写代码执行观察结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.oopsguy.mybatistopic;

import com.oopsguy.mybatistopic.bean.User;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class BeanResultMain {

public static void main(String[] args) throws IOException {
InputStream resStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

User user = new User();
user.setUsername("User1");
user.setPassword("User1_password");
sqlSession.insert("BeanResultMapper.insert", user);
user.setUsername("User2");
user.setPassword("User2_password");
sqlSession.insert("BeanResultMapper.insert", user);
sqlSession.commit();

user = sqlSession.selectOne("BeanResultMapper.selectByUsername", "User1");
System.out.println(user);

List<User> users = sqlSession.selectList("BeanResultMapper.selectAll");
System.out.println(users);

sqlSession.close();
}

}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
==>  Preparing: INSERT INTO user(id, username, password) VALUES(?, ?, ?) 
==> Parameters: null, User1(String), User1_password(String)
<== Updates: 1
==> Preparing: INSERT INTO user(id, username, password) VALUES(?, ?, ?)
==> Parameters: null, User2(String), User2_password(String)
<== Updates: 1
...
==> Parameters: User1(String)
<== Columns: id, username, password
<== Row: 8, User1, User1_password
<== Total: 1
com.oopsguy.mybatistopic.bean.User@6767c1fc
==> Preparing: SELECT * FROM user
==> Parameters:
<== Columns: id, username, password
<== Row: 8, User1, User1_password
<== Row: 9, User2, User2_password
<== Total: 2
[com.oopsguy.mybatistopic.bean.User@8bd1b6a, com.oopsguy.mybatistopic.bean.User@18be83e4

相关代码

https://github.com/oopsguy/mybaits-topic-example

后记

本文介绍mapper的基本使用方式,涉及常用的操作标签、参数映射和结果集映射配置,目前这些内容都是比较基础,mapper的内容不止这些,还有动态SQL、高级结果映射等内容都没有提到。如果您稍微变通一下,通过本次内容介绍,应该具备可以使用MyBatis做为项目持久层的能力。简单来说,mapper中定义的操作其实就像我们平时编写的DAO接口,您可以尝试把项目的DAO层移动MyBatis的mapper,应用到实际开发中。