Mybatis(一)——HelloWorld

本人的博客一向保持"傻瓜式"的风格。

循序渐进学Mybatis,先konw how,再konw why。先整体,再细节!

本文不讲难懂的概念,先通过一个案例,希望读者跟着本文一步一步的实现,再探究中间细节及原理。

相信只要认真阅读本文,一定会带给你收获!

一、Mybatis介绍

  MyBatis 是一款优秀的持久层框架,它支持定制化 SQL存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

  官网简介,好了,废话不多说。下面通过一个案例,学习如何使用 Mybatis 把数据库中一条记录查询出来并封装成一个普通的 Java 对象。

二、快速入门

1、下载jar包

  【Mybatis开发包】mybatis-3.4.1.jar

  【MySQL驱动包】mysql-connector-java-5.1.39-bin.jar

  做 Mybatis 开发,自然需要一些 Mybatis 的开发包。在 官网:http://www.mybatis.org/mybatis-3/ 中可以下载到。打开后,将网页语言选择简体中文(这里也有 Mybatis 的官网开发文档。个人觉得想提高技术的需要看文档),可以看到 Mybatis 目前所有的版本,本文采用 Mybatis-3.4.1 的版本做案例学习。下载好 mybatis-3.4.1.zip (开发包) 和 源代码(zip)(源码包)以便后面使用。mybatis-3.4.1.zip解压就有mybatis-3.4.1.jar。mysql-connector-java-5.1.39-bin.jar的获取本文就不做阐述了。

 

2、创建数据库和表

  本文采用MySQL数据库,由于需要从数据库中查询出一条记录封装到 Java 对象中,这里事先创建一个数据库,名为 mybatis ,创建一张表,名为 user ,表结构和数据如图所示。注意:红框处密码字段是 pass_word。

 

3、创建实体类

   打开 eclipse ,新建一个 Java Project , 创建一个实体类(User.java)用于封装数据,属性与数据库中的字段一一对应。

  User类代码如下。注意:此处密码属性是passWord。后面会作解释。

 1 package com.originator.model;
 2 
 3 public class User {
 4 
 5     private Integer id; // id号
 6     private String name; // 用户名
 7     private String passWord; // 密码
 8     private String email; // 邮箱
 9     private String gender; // 性别
10 
11     public Integer getId() {
12         return id;
13     }
14 
15     // get & set
16     public void setId(Integer id) {
17         this.id = id;
18     }
19 
20     public String getName() {
21         return name;
22     }
23 
24     public void setName(String name) {
25         this.name = name;
26     }
27 
28     public String getPassWord() {
29         return passWord;
30     }
31 
32     public void setPassWord(String passWord) {
33         this.passWord = passWord;
34     }
35 
36     public String getEmail() {
37         return email;
38     }
39 
40     public void setEmail(String email) {
41         this.email = email;
42     }
43 
44     public String getGender() {
45         return gender;
46     }
47 
48     public void setGender(String gender) {
49         this.gender = gender;
50     }
51 
52     @Override
53     public String toString() {
54         return "User [id=" + id + ", name=" + name + ", passWord=" + passWord + ", email=" + email + ", gender="
55                 + gender + "]";
56     }
57 
58 }

4、引入jar包

  在项目中新建一个文件夹用于存放 jar 包。右键—>new—>Folder,取名 lib 。将之前下载好的【Mybatis开发包】、【MySQL驱动包】复制到 lib 目录下,并选中后添加引入到项目中。右键—>Build Path—>Add to Build Path。

 

5、创建 mybatis 的全局配置文件

  由于实际开发中习惯将配置文件与 Java 文件分开,便于管理,所以新建一个资源文件。右键—>new—>Source Folder,取名resources。在 resources 目录下新建一个文件,右键—>new—>File,取名 mybatis-config.xml

  mybatis-config.xml 文件内容如下:

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE configuration
 3   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 <configuration>
 6     <environments default="development">
 7         <environment id="development">
 8             <transactionManager type="JDBC" />
 9             <dataSource type="POOLED">
10                 <property name="driver" value="com.mysql.jdbc.Driver" />
11                 <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
12                 <property name="username" value="root" />
13                 <property name="password" value="123456" />
14             </dataSource>
15         </environment>
16     </environments>
17     <mappers>
18         <mapper resource="org/mybatis/example/BlogMapper.xml" />
19     </mappers>
20 </configuration>
21 
22 mybatis-config.xml

  这里,其他标签都先不管,读者不需要理会,先照抄即可。只需看到 dataSource 标签下有4个属性标签 property ,name 是键,value是值。

  name = "driver",表示数据库驱动,值是固定的,"com.mysql.jdbc.Driver" 不需要修改。

  name = "url",表示数据库连接的url,值前面是 mysql 的前缀,localhost 表示本地电脑,3306是数据库默认的端口号,mybatis表示要连接的数据库名。由于本文之前创建数据库的时候数据库名称即为mybatis,所以这里写mybatis,读者需要根据自己的数据库所在位置修改相应的片段。若连接远程电脑数据库,需要把 localhost 修改为远程电脑的 ip 地址;若安装的数据库没有使用默认的端口号,需要把3306修改为数据库使用的端口号;把 mybatis 修改为自己的数据库名。

  name = "username",表示数据库的用户名,这里读者将值修改为自己的即可。

  name = "password",表示数据库的密码,这里读者将值修改为自己的即可。

6、编写测试类

  我们来创建一个测试类 UserTest.java。

  UserTest.java 文件内容如下:

 1 package com.originator.test;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 
 6 import org.apache.ibatis.io.Resources;
 7 import org.apache.ibatis.session.SqlSession;
 8 import org.apache.ibatis.session.SqlSessionFactory;
 9 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
10 import org.junit.Test;
11 
12 public class UserTest {
13 
14     @Test
15     public void test() throws IOException {
16         String resource = "mybatis-config.xml";
17         InputStream inputStream = Resources.getResourceAsStream(resource);
18         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
19 
20         SqlSession sqlSession = sqlSessionFactory.openSession();
21         sqlSession.selectOne(statement, parameter);
22     }
23 }

   这里,方法体的注解 @Test 表示这是一个 JUnit 单元测试,读者在编写时可能会显示红色下划波浪线错误,如图。这时,将鼠标停留在注解上,eclipse 会帮我们自动识别这个错误并提示解决方法,这里点击 Add JUnit 4 library to the build path 即可自动导入Junit 4的相关 jar 包。若读者不会使用 JUnit 单元测试,那么把方法修改为 public static void main(String args[]){} 写成 main 方法,使其可以单独运行即可。

  此时,可以看到,UserTest.java依然报错,原因是 openSession.selectOne(statement parameter); 的两个参数statementparameter没有正确传递。不慌,下面一一解答。这里不对其他代码做过多解释,读者照抄即可

  若前面读者下载了 Mybatis 的源码包,并加入到项目 Build Path 中,可以查看 selectOne(statement, parameter方法;若读者没有下载也无所谓,本文下面给出源码中这两个参数的注释,

  * @param statement Unique identifier matching the statement to use.
  * @param parameter A parameter object to pass to the statement.

  statement:sql的唯一标识

  parameter:执行 sql 用到的参数

  什么意思呢?似乎还是不太明白,没关系,读者继续按照下面的步骤阅读自然会有解答。

7、创建 mybatis 的映射文件

  在资源文件夹 resources 目录下新建一个文件,右键—>new—>File,取名 UserMapper.xml。 

  UserMapper.xml 文件内容如下:

1 <?xml version="1.0" encoding="UTF-8" ?>
2 <!DOCTYPE mapper
3   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5 <mapper namespace="haha">
6     <select id="hehe" resultType="com.originator.model.User">
7         select * from user where id = #{id}
8     </select>
9 </mapper>

  这里 1~4 行是 mybatis 映射文件的头,模板,照抄即可,读者可以先不理会。下面解释一个这个文件几个重要的地方。

  namespace = "haha"。名字空间,表示当前这个 mapper 标签名字,文本取名"haha"

  id = "hehe"。表示这个标签的唯一标识,标识名为"hehe"

  resultType = "com.originator.model.User"。表示查询的返回值类型。

  sql语句中 #{id},表示传递过来的参数中的id变量。

  不难理解:我们要查询一条 user 记录,那么它的 id 是多少呢?返回的是一条记录,如何封装成一个普通的 Java 对象呢?(这个 mybatis 帮我们做了,但我们必须指明返回值是什么类型?com.originator.model.User 就是创建的实体类的全类名,包名+类名

  那么问题来了?如何才能告诉程序,我们要执行这个标签的里面写的 sql 语句呢?

  这时,我们回到测试类 UserTest.java ,再来看看这两个参数。是不是有点明白什么了呢?

    statement:sql的唯一标识

    parameter:执行 sql 用到的参数

  是的,没错!

  statement 就是用来指明我要执行哪个文件(因为在项目中不止一个 XXXMapper.xml 文件)下的哪个sql标签(一个 XXXMapper.xml 中不止一个标签)。

  parameter 就是用来传递 sql 语句中的参数变量。

  改写 UserTest.java 文件内容如下:

 1 package com.originator.test;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 
 6 import org.apache.ibatis.io.Resources;
 7 import org.apache.ibatis.session.SqlSession;
 8 import org.apache.ibatis.session.SqlSessionFactory;
 9 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
10 import org.junit.Test;
11 
12 import com.originator.model.User;
13 
14 public class UserTest {
15 
16     @Test
17     public void test() throws IOException {
18         String resource = "mybatis-config.xml";
19         InputStream inputStream = Resources.getResourceAsStream(resource);
20         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
21 
22         SqlSession sqlSession = sqlSessionFactory.openSession();
23         User user = sqlSession.selectOne("haha.hehe", 1);
24 
25         System.out.println(user.toString()); // 打印一下
26     }
27 }

  传递方式是名字空间 + 标签id的形式。查询一下 user 表中 id 为 1 的记录。所以参数是 "haha.hehe",1。这里参数一,读者在 UserMapper.xml 中取名什么,这里就相应的修改成什么。不过这两个地方(namespace 和 id)不可随意取名,以后会详解。

  最后我们打印一下查询出来的 user ,看是否查询成功。单元测试方式,测试类代码空白处,右键—>Run As—>JUnit Test。如果是 main 函数直接运行就可以了。结果报如下错误:

  Cause: java.io.IOException: Could not find resource org/mybatis/example/BlogMapper.xml

  不要慌,稍微看一下就知道,程序说它找不到这个路径 org/mybatis/example/ 下的 BlogMapper.xml 文件。

8、将映射文件 UserMapper.xml 注册到全局配置文件 mybatis-config.xml

  这时,我们回到 mybatis-config.xml 。前文中只介绍了 dataSource 标签。下面介绍 mappers 标签,看标签名就知道,mapper:映射,s 表示复数,说明里面可以配很多个映射文件。<mapper resource="org/mybatis/example/BlogMapper.xml" />这里,我们并没有一个名为 BlogMapper.xml 的文件,所以刚刚程序才报错。我们要将我们自己的 mapper 文件注册到全局配置文件中。修改如下:

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

  这里,UserMapper.xml 文件就在类路径下,所以直接写文件名。如果 UserMapper.xml 文件在 XXX 包下,即需要带上包名:XXX/UserMapper.xml 。

  再次运行单元测试。执行结果如下:

  可以看到,将数据库中 id = 1 的用户查询出来了。不过有个问题,为什么 passWord 没有值呢?这里填上前文挖的坑。修改 UserMapper.xml 中的sql语句如下:红框表示给数据库中查询出来的字段 pass_word 取别名 passWord。再运行一下单元测试。

  可以看到:这下终于程序正确执行,得到了我们想要的结果。读者是不是也有点感觉呢?没错,mybatis 就是通过字段名和属性名一致来进行映射的。但是一般在项目中数据库的设计字段都遵循 xxx_yyy_zzz。而类属性名遵循 xxxYyyZzz,总不可能每次写 sql 语句的时候都加个别名把,那好麻烦哦。别着急,这里先挖坑。后续慢慢介绍。

三、面向接口式编程

  经过前面的案例,相信读者对 mybatis 的使用有了一定的了解,很多细节的地方可能还不太懂。本文也没有做解释,不过没关系,本文会在后续的章节中一一介绍。下面填一下前文挖的坑。

  前文中提到这两个地方(namespace 和 id)不可随意取名。

  不知读者是否有感觉到呢?执行 sql 语句的代码 openSession.selectOne(String statement, Object parameter) 这两个参数都是可以随意传的。

1、原因

  参数一:statement 每次传递的时候,编程人员,还要去寻找一下,到底要执行哪个映射文件下的哪个 sql 语句。如果在项目中,XXXMapper.xml 文件很多,一个 XXXMapper.xml 文件中又有多个 sql 标签。在代码中,statement 参数就会特别繁琐,且很不利于维护。而且,如果一不小心,将 statement 写错了,在程序中,String 类型是不会报错的,只有在运行,执行时才会报错,这也很不利于代码检查。

  参数二:parameter 是一个 Object 类型,同样在调用 selectOne() 方式时,编程人员也无法知道,这个参数到底传什么。还要按照 statement 参数找到对应的 sql 语句,查看之后才知道。如果 sql 语句稍微复杂一点,一时半会不能一下知道参数传什么,这也很不利用开发。同样,对于 Object ,在传递时,无论传递什么,在程序中,也不会报错,开发者也是无法立即得知参数是否传递正确。简单的说,如果一个方法的传参是 int 型,而调用者传递了一个 String ,则编译器会立马报错,可以提示编程人员应该传什么。

2、解决方案

  经过以上分析后,那么正确的方式应该怎样呢?不慌, mybatis 已经为了我们提供了 动态代理接口 的模式。如果读者不懂动态代理(Java 设计模式中的一种)也没有关系。只是一个名词而已,不要被吓到了。对于 Java 开发人员来说,接口,是我们很喜欢的一种方式,它只对调用者暴露了传递参数,以及得到一个什么样的返回值,而调用者不需要关心里面具体的实现。同样的,先请读者按照下面一步一步的实现。

  既然是面向接口式编程,那么我们先编写一个接口 UserMapper.java。放在包 com.originator.dao 下。这个主要是分层的概念,读者不必在意。

 

  UserMapper.java 文件内容如下:

1 package com.originator.dao;
2 
3 import com.originator.model.User;
4 
5 public interface UserMapper {
6 
7     public User getUserById(Integer id);
8 
9 }

   很自然的可以写出这么一个接口。有一个方法是 public User getUserById(Integer id); 该方法就是通过传入一个 id 返回一个User对象。方法名取的有意义即可。细心的读者可能已经发现了,这个文件的名称和 UserMapper.xml 一样,只是后缀不一样。是的,因为他们是配套的关系(个人的理解)。一个 UserMapper.java 与 一个 UserMapper.xml 对应。(也有的人喜欢命名为 UserDao.java,其实是一回事),为什么说他们是配套的关系呢? 我们先使用一下这个接口。那么,怎么来用它呢?

   在 UserTest.java 测试类中,新增一个测试方法 test0(),内容如下:

 1     @Test
 2     public void test0() throws IOException {
 3         String resource = "mybatis-config.xml";
 4         InputStream inputStream = Resources.getResourceAsStream(resource);
 5         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 6 
 7         SqlSession sqlSession = sqlSessionFactory.openSession();
 8         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
 9         User user = userMapper.getUserById(1);
10 
11         System.out.println(user.toString()); // 打印一下
12     }

  这个 test0() 方法在 openSession 这里就与 test() 方法不一样了。它有一个方法叫 getMapper(Class<T> type),里面传入的是一个泛型,这里我们传入接口的类类型。返回的就是一个接口,然后用接口调用其方法的方式得到一个 User。

  光这样就行了吗?当然不行,现在只是获取到了这个接口,调用了方法,这个方法是去执行哪个 sql 语句呢?还需要修改 UserMapper.xml 的 namespace 和 id。内容如下:

  namespace:为接口的全类名。

  id:为方法的方法名。 

  再执行一下测试方法 test0() ,结果如下,正确。

  至此,面向接口式编程实现完成。下面对一些细节作解释,可能有的读者已经有点感觉了。一个接口,是不能被实例化的,那它调用方法不是应该报空指针异常吗?可是没有。那说明:在这句中,openSession.getMapper(UserMapper.class); mybatis 为我们创建了一个代理对象。有兴趣的读者可以打印一下返回得到的 userMapper,显示为 org.apache.ibatis.binding.MapperProxy@9629756。就说明它是一个代理的对象。而且,读者稍微思考一下,也能大概猜到,这个代理对象的方法大概做了什么。

  姑且对 openSession.getMapper(UserMapper.class) 简单理解如下:

  通过反射得到 UserMapper 的全类名。在代理对象调用 getUserById(1)时,获取到方法名,然后依然执行 sqlSession.selectOne("com.originator.dao.UserMapper.getUserById", 1);

  所以,在接口若定义别的方法,如 deleteById(Integer id); 在 xml 中就需要编写一个 sql 语句,其标签 id 为 deleteById。所以个人认为他们是配套的关系,在实际开发中,也习惯将其文件名保持一致,方便阅读,可以使开发者一眼就知道,哪个接口文件去找哪个 xml 文件。

四、总结

  先对一些细节作解释。前文没有对 test() 方法中的代码写注释,也没有做过多的解释。读者可以简单理解一下:

  String resource = "mybatis-config.xml"; // 定义了 mybatis 全局配置文件的路径,然后通过 Java io流的方式读取了 xml 文件的内容,得到了一个 sqlSessionFactory (sql会话工厂),从工厂当中获取了一次会话(sqlSession),这是用于与数据库交互的,该对象中有很多方法,例如:

  insert(statement, parameter);

  delete(statement, parameter);

  update(statement, parameter);

  这里不一一介绍,相信读者自己也能明白。而代理模式前面也大概简单作了解释,这里不深入研究了(有兴趣的读者可以先了解一下 Java 的动态代理原理),在后续的篇幅中,会进一步做研究,包括现在一些细节的东西和挖的坑。

  在实际项目中,都是使用的 Mapper 接口开发,程序员只需要编写 Mapper 接口和 Mapper.xml。Mapper接口的编写需要遵守相关规范:

  mapper.xml中的名字空间 = Mapper接口类的全路径;
  mapper.xml中的 sql 标签 id = mapper接口类的方法名;

posted @ 2018-09-27 10:40  Craftsman-L  阅读(379)  评论(0编辑  收藏  举报