本文首发于2019年5月25日,后续会考虑添加Jersey库来实现Java后端REST接口服务。
Mybatis是Java平台上的ORM框架,用于实现Java对象到SQL语句的映射,目前阿里巴巴使用的就是Mybatis。
一般来说,使用Mybatis的场合基本都是集成在Spring框架中的,且都是由Spring来执行初始化等一系列准备工作。因此这篇博文主要讲的是如何从零开始搭建一个使用Mybatis的项目。

起步

Mybatis有中文文档,行文风格也浅显易懂,甚至还有代码实例。
如果本文内容未能解决你的疑问,查阅官方文档是一个不错的选择。

使用Mybatis的第一步就是将它引入到项目中,下面两种方法选择一种即可:
如果使用jar包的方式,在其官方Github发布页有最新版构建完成的jar包可用;
如果使用Maven引入,将下面的代码添加到pom.xml文件中:

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>

此外,Mybatis不提供连接数据库的组件,因此需要自行准备连接数据库的连接器。
一般而言,连接数据库都是使用JDBC或是DBCP,可以在MySQL官网找到Connector/J下载地址。

这里以Eclipse创建项目为例,先新建一个基本Java项目,取名为MybatisDemo。
本文使用Mybatis的3.5.1版本,下载jar包,并添加到构建路径中;
本文使用MySQL的8.0.11版本的连接器,下载jar包,并添加到构建路径中。

然后项目结构如下:

然后去MySQL数据库里建立个名为mybatisdemo的数据库。
(阿里巴巴开发手册告诉我们,因为Linux和Windows下MySQL对大小写字段的处理方式不同,所以与数据库相关的字段一定要全部小写,避免出现问题。)

定义模型Model

这里以用户模型为例,类名叫User,假定User有四个属性,分别是:
ID(uuid,主键,String类型);
用户名(userName,String类型);
创建日期(createDate,Date类型);
权限(userAccess,int类型)。

创建一个名为pojo的包,在里面新建一个User.java的文件,内容形如:

1
2
3
4
5
6
7
8
9
public class User {
private String uuid;
private String userName;
private Date createDate;
private int userAccess;

// 别忘了用Eclipse自动生成构造函数、Setter、Getter等
// 这里省略了
}

记得重载下.toString( ),方便之后的输出打印,这也是阿里巴巴Java开发手册中规定的内容。

然后到MySQL数据库,找到mybatisdemo库,创建一个user_info表,字段配置如下:

注意数据表里的字段应和User类的字段对应。
例如,createDate对应create_date,或者对应createdate也行,这样名称对应,之后做ORM映射就会方便很多。字段不对应的话会稍微麻烦一些。
注意User类中不包含pwd字段,因为密码不应该储存在用户Model里面。

定义映射

一般来说,映射这一块叫Mapper或者Mapping。
在网上的教程基本都是用XML+Java的方式,XML文件定义SQL语句到Java对象的映射,然后Java写一个接口来表示映射的操作;
本文主要以纯Java注解方式为主。

建立一个名为mapper的包,在其中创建一个名为UserMapper.javaJava接口文件,内容如下:

1
2
3
4
5
6
7
8
9
10
public interface UserMapper {
@Select("select uuid,user_name,create_date,user_access from user_info where uuid=#{uuid}")
public User getUserById(String uuid);

@Select("select uuid,user_name,create_date,user_access from user_info")
public List<User> getAllUsers();

@Insert("insert into user_info values(#{user.uuid},#{user.userName},#{pwd},#{user.createDate},#{user.userAccess})")
public int createUser(@Param("user") User user, @Param("pwd") String pwd);
}

注意这个UserMapper是个接口。这里就假设我们只有3个方法:根据ID获取用户、获取所有用户、插入一个用户。

其中前两个方法最简单,只需要用select语句,最多加一个where条件;
查询操作,使用的是@Select注解,只需要在注解中把SQL语句写进去即可。

1
2
@Select("select uuid,user_name,create_date,user_access from user_info where uuid=#{uuid}")
public User getUserById(String uuid);

根据阿里巴巴Java手册,ORM框架的select语句要列出所有字段,禁止使用*,所以这里把所有字段都写出来;
这里注解中的#{uuid}占位符会被ORM框架底层转换成?问号,然后再传入参数,防止SQL注入;
还可以使用${uuid}形式,这种形式的占位符Mybatis不会做参数化,阿里巴巴Java手册中明确禁用这种方式。

第三个方法是创建一个用户,代码如下:

1
2
@Insert("insert into user_info values(#{user.uuid},#{user.userName},#{pwd},#{user.createDate},#{user.userAccess})")
public int createUser(@Param("user") User user, @Param("pwd") String pwd);

因为创建用户是插入操作,因此使用@Insert注解,否则数据插入不进去;
这里因为方法接受一个User对象和一个表示密码的字符串,参数比较多,而且需要解构User对象,因此注解的写法比较复杂。

首先给两个参数加上@Param注解,表示它们在SQL语句中对应什么字段,否则Mybatis无法获取传入的User对象的详细属性,然后再在注解语句中把insert的values内容依次列出:
values(#{user.uuid},#{user.userName},#{pwd},#{user.createDate},#{user.userAccess})
这里的#{user.uuid}#{user.createDate}等写法就类似Java代码,表示传入的user对象的ID和创建日期,而pwd表示是密码字符串。

这样一个有关User对象的映射便完成了,目前只提供了3个方法,可以仿照这个再写出更新、删除的映射。
注意更新映射用@Update注解,删除映射用@Delete注解。
因为映射是接口,它无法定义方法体,因此无法封装业务逻辑,具体业务逻辑请在Service层完成。

配置Mybatis

定义好了上面的模型、映射,然后就需要配置Mybatis了,不然Mybatis不知道数据库的地址、不知道数据库连接密码、不知道如何加载映射文件,必定是无法使用的。

创建一个名为app的包,在其中创建一个mybatis.xml文件,填入以下内容:

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdemo?serverTimezone=UTC" />
<property name="username" value="数据库的登录名(比如root)" />
<property name="password" value="数据库的登录密码" />
</dataSource>
</environment>
</environments>

<mappers>
<package name="cc.paperplane.mybatisdemo.mapper" />
</mappers>
</configuration>

<setting name="mapUnderscoreToCamelCase" value="true" />表示开启使用下划线映射驼峰式命名:例如Java字段createDate映射为数据库字段的create_date,这个我们有用到,所以需要开启。
Mybatis提供了很多设置项,查看可以按需开启。

<environments default="development">表示默认使用development模式的环境,实际开发中,可以设置开发和生产环境使用不同的数据库。

<property name="driver" value="com.mysql.cj.jdbc.Driver" />表示Java加载连接器的类路径,类似于class.forName( )加载的路径。

<mappers>里面表示加载Mapper映射器的方式,一般有按限定名加载XML、按绝对路径加载XML、按限定名加载Java接口文件、按包加载Java接口这四种方式,具体使用方式可以参考官网文档
因为我们之前有创建一个mapper包,可以把所有mapper接口都放在这个包里面,所以使用按包加载Java接口这种方式,配置项写成<package name="cc.paperplane.mybatisdemo.mapper" />

把这个XML都配置好,数据库名、登录名、密码、连接件加载路径等都要确保正确,便可以开始下一步,编写项目启动代码,加载Mybatis配置并启动实例。

启动项目

在刚才创建的app包下创建一个app.java文件,在其中定义main方法:

1
2
3
4
5
public static void main(String[] args) throws IOException {
final var resource = "cc/paperplane/mybatisdemo/app/mybatis.xml";
var inputStream = Resources.getResourceAsStream(resource);
var sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}

这里的resource表示的是之前配置XML文件的限定路径,Eclipse右键这个XML文件选择复制限定名即可,只需要取其相对于src/目录的相对路径即可;
然后后面的代码将它转化为文件流,并传入SqlSessionFactoryBuilder构造器类,完成一个SqlSessionFactory工厂类的创建;
之后的SQL会话都将使用这个工厂类来实现。

此时的项目结构(前面的cc.paperplane.前缀可以不用):

增删改查实际操作

继续改写app.java文件,实现插入、查询功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws IOException {
final var resource = "cc/paperplane/mybatisdemo/app/mybatis.xml";
var inputStream = Resources.getResourceAsStream(resource);
var sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 注意下面这一句,sqlSessionFactory.openSession(true) 传入true表示开启自动提交
try (var session = sqlSessionFactory.openSession(true)) {
var newUser = new User("0000001", "Tester", new Date(), 1);
session.getMapper(UserMapper.class).createUser(newUser, "abcdef");

var allUsers = session.getMapper(UserMapper.class).getAllUsers();
allUsers.forEach(t -> System.out.println(t));

} catch (Exception e) {
e.printStackTrace();
}
}

这里使用TryWithResource的方式来使用SqlSession,否则代码会变得极其难看。

注意try (var session = sqlSessionFactory.openSession(true))这一句,这里sqlSessionFactory.openSession(true)是带有一个参数true的,表示开启自动提交模式,因此增删改操作是自动执行的;
如果不传入这个true参数,增删改操作都需要在后面加一句session.commit( );代码,否则操作是无效的。

这段代码创建了一个用户并插入数据库,然后查询列出所有数据库里的用户;
执行结果如下:

如果你之前没有重载User.toString( )方法,这里可能打印不出来属性,所以一定要养成重载POJO类的.toStirng( )方法的好习惯,方便调试。

这段代码需要先从SqlSessionFactory中创建一个SqlSession对象,然后用这个对象执行操作;
它调用.getMapper( )方法,传入一个Mapper的接口的.class属性,这样可以利用Java泛型调用其定义的各个方法,Mybatis会自动完成SQL查询参数化、查询参数解构等操作。

总结

使用Java注解来实现Mybatis映射是一种非常轻量的方式,它省去了繁琐的XML配置,但是在实现一些复杂功能(例如,Java注解必须是编译时确定的值,因此它很难支持动态SQL)时候比较困难。

具体使用方式可以参照Mybatis官方JavaAPI文档,这里面有很多示例。

Mybatis的配置项其实全部可以使用Java代码来实现,但是一般开发软件,配置和代码要分离开,所以本文保留Mybatis配置文件的用法。