zoukankan      html  css  js  c++  java
  • 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接口类的方法名;

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    Java实现 LeetCode 50 Pow(x,n)
    Java实现 LeetCode 50 Pow(x,n)
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 47 全排列 II(二)
    Java实现 LeetCode 47 全排列 II(二)
  • 原文地址:https://www.cnblogs.com/originator/p/9711835.html
Copyright © 2011-2022 走看看